001package Torello.HTML;
002
003import Torello.Java.StringParse;
004import Torello.Java.StrCmpr;
005import Torello.Java.StrFilter;
006
007import Torello.HTML.NodeSearch.CSSStrException;
008import Torello.HTML.NodeSearch.TextComparitor;
009
010import java.util.Vector;
011import java.util.Properties;
012import java.util.Map;
013
014import java.util.regex.Pattern;
015import java.util.regex.Matcher;
016
017import java.util.stream.Stream;
018
019import javax.management.AttributeNotFoundException;
020
021import java.util.function.Predicate;
022
023import Torello.HTML.HelperPackages.parse.HTMLRegEx;
024import Torello.HTML.HelperPackages.TagNode.*;
025
026/**
027 * Represents an HTML Element Tag, and is the flagship class of the Java-HTML Library.
028 * 
029 * <EMBED CLASS='external-html' DATA-FILE-ID=TAG_NODE>
030 * <EMBED CLASS='external-html' DATA-FILE-ID=HTML_NODE_SUB_IMG>
031 * 
032 * @see TextNode
033 * @see CommentNode
034 * @see HTMLNode
035 */
036@Torello.JavaDoc.JDHeaderBackgroundImg(EmbedTagFileID="HTML_NODE_SUBCLASS")
037public final class TagNode 
038    extends HTMLNode 
039    implements CharSequence, java.io.Serializable, Cloneable, Comparable<TagNode>
040{
041    /** <EMBED CLASS='external-html' DATA-FILE-ID=SVUID> */
042    public static final long serialVersionUID = 1;
043
044
045    // ********************************************************************************************
046    // ********************************************************************************************
047    // NON-STATIC FIELDS
048    // ********************************************************************************************
049    // ********************************************************************************************
050
051
052    /** <EMBED CLASS='external-html' DATA-FILE-ID=TAGNODE_TOK> */
053    public final String tok;
054
055    /** <EMBED CLASS='external-html' DATA-FILE-ID=TAGNODE_IS_CLOSING> */
056    public final boolean isClosing;
057
058
059
060    // ********************************************************************************************
061    // ********************************************************************************************
062    // Package-Private Constructors - NO ERROR CHECKING DONE WHATSOEVER
063    // ********************************************************************************************
064    // ********************************************************************************************
065
066
067    // ONLY USED BY THE "TagNodeHelpers" and in conjunction with "Generate Element String"
068    // 
069    // It presumes that the node was properly constructed and needs to error-checking
070    // It is only used for opening TagNode's
071
072    TagNode(String tok, String str)
073    {
074        super(str);
075
076        this.tok        = HTMLTags.getTag_MEM_HEAP_CHECKOUT_COPY(tok);
077        this.isClosing  = false;
078    }
079
080    // USED-INTERNALLY - bypasses all checks.  used when creating new HTML Element-Names
081    // ONLY: class 'HTMLTags' via method 'addTag(...)' shall ever invoke this constructor.
082    //
083    // NOTE: This only became necessary because of the MEM_COPY_HEAP optimization.  This
084    //       optimization expects that there is already a TagNode with element 'tok' in
085    //       the TreeSet, which is always OK - except for the method that CREATES NEW HTML
086    //       TAGS... a.k.a. HTMLTags.addTag(String).
087
088    TagNode(String token, TC openOrClosed)
089    {
090        super("<" + ((openOrClosed == TC.ClosingTags) ? "/" : "") + token + ">");
091
092        // ONLY CHANGE CASE HERE, NOT IN PREVIOUS-LINE.  PAY ATTENTION.  
093        this.tok = token.toLowerCase();
094
095        this.isClosing = (openOrClosed == TC.ClosingTags) ? true : false;
096    }
097
098
099    // ********************************************************************************************
100    // ********************************************************************************************
101    // Public Constructors
102    // ********************************************************************************************
103    // ********************************************************************************************
104
105
106    /**
107     * <EMBED CLASS='external-html' DATA-FILE-ID=TN_C_DESC_1>
108     * 
109     * @param s Any valid HTML tag, for instance: {@code <H1>, <A HREF="somoe url">,
110     * <DIV ID="some id">} etc...
111     * 
112     * @throws MalformedTagNodeException If the passed {@code String} wasn't valid - meaning <I>it
113     * did not match the regular-expression {@code parser}.</I> 
114     * 
115     * @throws HTMLTokException If the {@code String} found where the usual HTML token-element is
116     * situated <I>is not a valid HTML element</I> then the {@code HTMLTokException} will be
117     * thrown.
118     * 
119     * @see HTMLTags#getTag_MEM_HEAP_CHECKOUT_COPY(String)
120     */
121    public TagNode(String s)
122    {
123        super(s);
124
125        // If the second character of the string is a forward-slash, this must be a closing-element
126        // For Example: </SPAN>, </DIV>, </A>, etc...
127
128        isClosing = s.charAt(1) == '/';
129
130        // This is the Element & Attribute Matcher used by the RegEx Parser.  If this Matcher
131        // doesn't find a match, the parameter 's' cannot be a valid HTML Element.  NOTE: The
132        // results of this matcher are also used to retrieve attribute-values, but here below,
133        // its results are ignored.
134
135        Matcher m = HTMLRegEx.P1.matcher(s);
136
137        if (! m.find()) throw new MalformedTagNodeException(
138            "The parser's regular-expression did not match the constructor-string.\n" +
139            "The exact input-string was: [" + s + "]\n" +
140            "NOTE:  The parameter-string is included as a field (ex.str) to this Exception.", s
141        );
142
143        if ((m.start() != 0) || (m.end() != s.length()))
144
145            throw new MalformedTagNodeException(
146                "The parser's regular-expression did not match the entire-string-length of the " +
147                "string-parameter to this constructor: m.start()=" + m.start() + ", m.end()=" + 
148                m.end() + ".\nHowever, the length of the Input-Parameter String was " +
149                '[' + s.length() + "]\nThe exact input-string was: [" + s + "]\nNOTE: The " +
150                "parameter-string is included as a field (ex.str) to this Exception.", s
151            );
152
153        // MINOR/MAJOR IMPROVEMENT... REUSE THE "ALLOCATED STRING TOKEN" from HTMLTag's class
154        // THINK: Let the Garbage Collector take out as many duplicate-strings as is possible..
155        // AND SOONER.  DECEMBER 2019: "Optimization" or ... "Improvement"
156        // 
157        // Get a copy of the 'tok' string that was already allocated on the heap; (OPTIMIZATON)
158        //
159        // NOTE: There are already myriad strings for the '.str' field.
160        // 
161        // ALSO: Don't pay much attention to this line if it doesn't make sense... it's not
162        //       that important.  If the HTML Token found was not a valid HTML5 token, this field
163        //       will be null.
164        // 
165        // Java 14+ has String.intern() - that's what this is....
166
167        this.tok = HTMLTags.getTag_MEM_HEAP_CHECKOUT_COPY(m.group(1));
168
169        // Now do the usual error check.
170        if (this.tok == null) throw new HTMLTokException(
171            "The HTML Tag / Token Element that is specified by the input string " +
172            "[" + m.group(1).toLowerCase() + "] is not a valid HTML Element Name.\n" +
173            "The exact input-string was: [" + s + "]"
174        );
175    }
176
177    /**
178     * Convenience Constructor.
179     * <BR />Invokes: {@link #TagNode(String, Properties, Iterable, SD, boolean)}
180     * <BR />Passes: null to the Boolean / Key-Only Attributes {@code Iterable}
181     */
182    public TagNode(
183            String      tok,
184            Properties  attributes,
185            SD          quotes,
186            boolean     addEndingForwardSlash
187        ) 
188    {
189        this(
190            tok,
191            GeneralPurpose.generateElementString(
192                tok,
193                attributes,
194                null, // keyOnlyAttributes,
195                quotes,
196                addEndingForwardSlash
197            ));
198    }
199
200    /**
201     * <EMBED CLASS='external-html' DATA-FILE-ID=TN_C_DESC_2>
202     * @param tok                   <EMBED CLASS='external-html' DATA-FILE-ID=TN_C_TOK>
203     * @param attributes            <EMBED CLASS='external-html' DATA-FILE-ID=TN_C_ATTRIBUTES>
204     * @param keyOnlyAttributes     <EMBED CLASS='external-html' DATA-FILE-ID=TN_C_KO_ATTRIBUTES>
205     * @param quotes                <EMBED CLASS='external-html' DATA-FILE-ID=TN_C_QUOTES>
206     * @param addEndingForwardSlash <EMBED CLASS='external-html' DATA-FILE-ID=TN_C_AEFS>
207     * @throws InnerTagKeyException <EMBED CLASS='external-html' DATA-FILE-ID=IT_KEY_EX_PROP_TN>
208     * @throws QuotesException      <EMBED CLASS='external-html' DATA-FILE-ID=QEX>
209     * 
210     * @throws HTMLTokException if an invalid HTML 4 or 5 token is not present
211     * <B>(check is {@code CASE_INSENSITIVE})</B>, or a token which has been registered with class
212     * {@code HTMLTags}.
213     * 
214     * @see InnerTagKeyException#check(String, String)
215     * @see QuotesException#check(String, SD, String)
216     */
217    public TagNode(
218            String              tok,
219            Properties          attributes,
220            Iterable<String>    keyOnlyAttributes,
221            SD                  quotes,
222            boolean             addEndingForwardSlash
223        )
224    {
225        this(
226            tok,
227            GeneralPurpose.generateElementString
228                (tok, attributes, keyOnlyAttributes, quotes, addEndingForwardSlash)
229        );
230    }
231
232
233    // ********************************************************************************************
234    // ********************************************************************************************
235    // HTMLNode Overidden - Loop & Stream Optimization Methods
236    // ********************************************************************************************
237    // ********************************************************************************************
238
239
240    /**
241     * This method identifies that {@code 'this'} instance of (abstract parent-class)
242     * {@link HTMLNode} is, indeed, an instance of sub-class {@code TagNode}.
243     *
244     * <BR /><BR /><B CLASS=JDDescLabel>Final Method:</B>
245     * 
246     * <BR />This method is final, and cannot be modified by sub-classes.
247     * 
248     * @return This method shall always return {@code TRUE}  It overrides the parent-class
249     * {@code HTMLNode} method {@link #isTagNode()}, which always returns {@code FALSE}.
250     */
251    @Override
252    public final boolean isTagNode() { return true; }
253
254    /**
255     * <EMBED CLASS='external-html' DATA-FILE-ID=TN_IF_TN_DESC>
256     * <BR /><BR /><B CLASS=JDDescLabel>Final Method:</B>
257     * <BR />This method is final, and cannot be modified by sub-classes.
258     * @return <EMBED CLASS='external-html' DATA-FILE-ID=TN_IF_TN_RET>
259     */
260    @Override
261    public final TagNode ifTagNode() { return this; }
262
263    /**
264     * <EMBED CLASS='external-html' DATA-FILE-ID=TN_OPENTAG_PWA_DESC>
265     * <BR /><BR /><B CLASS=JDDescLabel>Final Method:</B>
266     * <BR />This method is final, and cannot be modified by sub-classes.
267     * @return <EMBED CLASS='external-html' DATA-FILE-ID=TN_OPENTAG_PWA_RET>
268     */
269    @Override
270    public final TagNode openTagPWA()
271    {
272        // Closing TagNode's simply may not have attributes
273        if (this.isClosing) return null;
274
275        // A TagNode whose '.str' field is not AT LEAST 4 characters LONGER than the length of the
276        // HTML-Tag / Token, simply cannot have an attribute.
277        //
278        // NOTE: Below is the shortest possible HTML tag that could have an attribute.
279        // COMPUTE: '<' + TOK.LENGTH + SPACE + 'c' + '>'
280
281        if (this.str.length() < (this.tok.length() + 4)) return null;
282
283        // This TagNode is an opening HTML tag (like <DIV ...>, rather than </DIV>),
284        // and there are at least two additional characters after the token, such as: <DIV A...>
285        // It is not guaranteed that this tag has attributes, but it is possibly - based on these
286        /// optimization methods, and further investigation would have merit.
287
288        return this;
289    }
290
291    /**
292     * This is a loop-optimization method that makes finding opening {@code TagNode's} - <B>with
293     * attribute-</B><B STYLE='color: red;'>values</B> - quites a bit faster.  All {@link HTMLNode}
294     * subclasses implement this method, but only {@code TagNode} instances will ever return a
295     * non-null value.
296     * 
297     * <BR /><BR /><B CLASS=JDDescLabel>Final Method:</B>
298     * 
299     * <BR />This method is final, and cannot be modified by sub-classes.
300     * 
301     * @return Returns null if and only if {@code 'this'} instance' {@link #isClosing} field is
302     * false.  When a non-null return-value is acheived, that value will always be {@code 'this'}
303     * instance.
304     */
305    @Override
306    public final TagNode openTag()
307    { return isClosing ? null : this; }
308
309    /**
310     * This method is an optimization method that overrides the one by the same name in class
311     * {@link HTMLNode}.
312     * 
313     * {@inheritdoc}
314     */
315    @Override
316    public boolean isOpenTagPWA()
317    {
318        if (this.isClosing) return false;
319        if (this.str.length() < (this.tok.length() + 4)) return false;
320        return true;
321    }
322
323    /**
324     * This method is an optimization method that overrides the one by the same name in class
325     * {@link HTMLNode}.
326     * 
327     * {@inheritdoc}
328     */
329    @Override
330    public boolean isOpenTag()
331    { return ! isClosing; }
332
333
334    // ********************************************************************************************
335    // ********************************************************************************************
336    // isTag
337    // ********************************************************************************************
338    // ********************************************************************************************
339
340
341    /**
342     * <EMBED CLASS='external-html' DATA-FILE-ID=TN_IS_TAG1_DESC>
343     * @param possibleTags  A non-null list of potential HTML tags to be checked again {@link #tok}
344     * @return              <EMBED CLASS='external-html' DATA-FILE-ID=TN_IS_TAG1_RET>
345     * @see                 #tok
346     */
347    public boolean isTag(String... possibleTags)
348    { 
349        for (String htmlTag : possibleTags) if (htmlTag.equalsIgnoreCase(this.tok)) return true;
350        return false;
351    }
352
353    /**
354     * <EMBED CLASS='external-html' DATA-FILE-ID=TN_IS_TAG_EX1_DESC>
355     * @param possibleTags  A non-null list of potential HTML tags to be checked again {@link #tok}
356     * @return              <EMBED CLASS='external-html' DATA-FILE-ID=TN_IS_TAG_EX1_RET>
357     * @see                 #tok
358     * @see                 #isTag(String[])
359     */
360    public boolean isTagExcept(String... possibleTags)
361    { 
362        for (String htmlTag : possibleTags) if (htmlTag.equalsIgnoreCase(this.tok)) return false;
363        return true;
364    }
365
366    /**
367     * <EMBED CLASS='external-html' DATA-FILE-ID=TN_IS_TAG2_DESC>
368     * @param tagCriteria   <EMBED CLASS='external-html' DATA-FILE-ID=TN_IS_TAG2_PARAM>
369     * @param possibleTags  A non-null list of potential HTML tags to be checked again {@link #tok}
370     * @return              <EMBED CLASS='external-html' DATA-FILE-ID=TN_IS_TAG2_RET>
371     * @see                 #tok
372     */
373    public boolean isTag(TC tagCriteria, String... possibleTags)
374    { return IsTag.isTag(this, tagCriteria, possibleTags); }
375
376    /**
377     * <EMBED CLASS='external-html' DATA-FILE-ID=TN_IS_TAG_EX2_DESC>
378     * @param tagCriteria   <EMBED CLASS='external-html' DATA-FILE-ID=TN_IS_TAG_EX2_PARAM>
379     * @param possibleTags  A non-null list of potential HTML tags to be checked again {@link #tok}
380     * @return              <EMBED CLASS='external-html' DATA-FILE-ID=TN_IS_TAG_EX2_RET>
381     * @see                 #tok
382     */
383    public boolean isTagExcept(TC tagCriteria, String... possibleTags)
384    { return IsTag.isTagExcept(this, tagCriteria, possibleTags); }
385
386
387    // ********************************************************************************************
388    // ********************************************************************************************
389    // Main Method 'AV'
390    // ********************************************************************************************
391    // ********************************************************************************************
392
393
394    /**
395     * <EMBED CLASS='external-html' DATA-FILE-ID=TN_AV_DESC>
396     * <EMBED CLASS='external-html' DATA-FILE-ID=TN_AV_DESC_EXAMPLE>
397     * @param innerTagAttribute <EMBED CLASS='external-html' DATA-FILE-ID=TN_AV_ITA>
398     * @return                  <EMBED CLASS='external-html' DATA-FILE-ID=TN_AV_RET>
399     * @see StringParse#ifQuotesStripQuotes(String)
400     */
401    public String AV(String innerTagAttribute)
402    { return GetSetAttr.AV(this, innerTagAttribute, false); }
403
404    /**
405     * Identical to {@link #AV(String)}, except that if the
406     * Attribute-<B STYLE='color: red;'>value</B> had quotes surrounding it, those are included in
407     * the returned {@code String}.
408     */
409    // To-Do, finish this after brekfast
410    // public String preserveQuotesAV(String innerTagAttribute)
411    // { return GetSetAttr.AV(this, innerTagAttribute, true); }
412
413    /**
414     * <B STYLE='color: red;'>AVOPT: Attribute-Value - Optimized</B>
415     * 
416     * <BR /><BR /> This is an "optimized" version of method {@link #AV(String)}.  This method does
417     * the exact same thing as {@code AV(...)}, but leaves out parameter-checking and
418     * error-checking. This is used internally (and repeatedly) by the NodeSearch Package Search
419     * Loops.
420     * 
421     * @param innerTagAttribute This is the inner-tag / attribute <B STYLE='color: red;'>name</B>
422     * whose <B STYLE='color: red;'>value</B> is hereby being requested.
423     * 
424     * @return {@code String}-<B STYLE='color: red;'>value</B> of this inner-tag / attribute.
425     * 
426     * @see StringParse#ifQuotesStripQuotes(String)
427     * @see #str
428     */
429    public String AVOPT(String innerTagAttribute)
430    {
431        // COPIED DIRECTLY FROM class TagNode, leaves off initial tests.
432
433        // Matches "Attribute / Inner-Tag Key-Value" Pairs.
434        Matcher m = AttrRegEx.KEY_VALUE_REGEX.matcher(this.str);
435
436        // This loop iterates the KEY_VALUE PAIRS THAT HAVE BEEN FOUND.
437        /// NOTE: The REGEX Matches on Key-Value Pairs.
438
439        while (m.find())
440
441            // m.group(2) is the "KEY" of the Attribute KEY-VALUE Pair
442            // m.group(3) is the "VALUE" of the Attribute.
443
444            if (m.group(2).equalsIgnoreCase(innerTagAttribute))
445                return StringParse.ifQuotesStripQuotes(m.group(3));
446
447        // This means the attribute name provided to parameter 'innerTagAttribute' was not found.
448        return null;
449    }
450
451
452    // ********************************************************************************************
453    // ********************************************************************************************
454    // Attribute Modify-Value methods
455    // ********************************************************************************************
456    // ********************************************************************************************
457
458
459    /**
460     * <EMBED CLASS='external-html' DATA-FILE-ID=TN_SET_AV_DESC>
461     * @param attribute <EMBED CLASS='external-html' DATA-FILE-ID=TN_SET_AV_ATTR>
462     * 
463     * @param value Any valid attribute-<B STYLE='color: red;'>value</B>.  This parameter may not
464     * be null, or a {@code NullPointerException} will throw.
465     * 
466     * @param quote                     <EMBED CLASS='external-html' DATA-FILE-ID=TN_SET_AV_QUOTE>
467     * @throws InnerTagKeyException     <EMBED CLASS='external-html' DATA-FILE-ID=IT_KEY_EX_TN>
468     * @throws QuotesException          <EMBED CLASS='external-html' DATA-FILE-ID=QEX>
469     * @throws ClosingTagNodeException  <EMBED CLASS='external-html' DATA-FILE-ID=CTNEX>
470     * 
471     * @throws HTMLTokException If an invalid HTML 4 or 5 token is not present 
472     * (<B>{@code CASE_INSENSITIVE}</B>).
473     * 
474     * @return <EMBED CLASS='external-html' DATA-FILE-ID=TN_SET_AV_RET>
475     * @see ClosingTagNodeException#check(TagNode)
476     * @see #setAV(Properties, SD)
477     * @see #str
478     * @see #isClosing
479     */
480    public TagNode setAV(String attribute, String value, SD quote)
481    { return GetSetAttr.setAV(this, attribute, value, quote); }
482
483    /**
484     * <EMBED CLASS='external-html' DATA-FILE-ID=TN_SET_AV2_DESC>
485     * @param attributes These are the new attribute <B STYLE='color: red;'>key-value</B> pairs to
486     * be inserted.
487     * 
488     * @param defaultQuote <EMBED CLASS='external-html' DATA-FILE-ID=TN_SET_AV2_DQ_PARAM>
489     * @throws InnerTagKeyException <EMBED CLASS='external-html' DATA-FILE-ID=IT_KEY_EX_PROP_TN>
490     * 
491     * @throws QuotesException if there are "quotes within quotes" problems, due to the
492     * <B STYLE='color: red;'>values</B> of the <B STYLE='color: red;'>key-value</B> pairs.
493     * 
494     * @throws HTMLTokException if an invalid HTML 4 or 5 token is not present 
495     * <B>({@code CASE_INSENSITIVE})</B>
496     * 
497     * @throws ClosingTagNodeException <EMBED CLASS='external-html' DATA-FILE-ID=CTNEX>
498     *
499     * @return <EMBED CLASS='external-html' DATA-FILE-ID=TN_SET_AV2_RET>
500     * @see ClosingTagNodeException#check(TagNode)
501     * @see #setAV(String, String, SD)
502     * @see #isClosing
503     */
504    public TagNode setAV(Properties attributes, SD defaultQuote)
505    { return GetSetAttr.setAV(this, attributes, defaultQuote); }
506
507    /**
508     * <EMBED CLASS='external-html' DATA-FILE-ID=TN_APD_AV_DESC>
509     * @param attribute                 <EMBED CLASS=external-html DATA-FILE-ID=TN_APD_AV_P_ATTR>
510     * @param appendStr                 <EMBED CLASS=external-html DATA-FILE-ID=TN_APD_AV_P_APDSTR>
511     * @param startOrEnd                <EMBED CLASS=external-html DATA-FILE-ID=TN_APD_AV_P_S_OR_E>
512     * @param quote                     <EMBED CLASS=external-html DATA-FILE-ID=TGND_QUOTE_EXPL>
513     *                                  <EMBED CLASS=external-html DATA-FILE-ID=TN_APD_AV_P_QXTRA>
514     * @return                          <EMBED CLASS=external-html DATA-FILE-ID=TN_APD_AV_RET>
515     * @see                             #AV(String)
516     * @see                             #setAV(String, String, SD)
517     * @see                             ClosingTagNodeException#check(TagNode)
518     * @throws ClosingTagNodeException  <EMBED CLASS='external-html' DATA-FILE-ID=CTNEX>
519     * 
520     * @throws QuotesException The <B><A HREF=#QUOTEEX>rules</A></B> for quotation usage apply
521     * here too, and see that explanation for how how this exception could be thrown.
522     */
523    public TagNode appendToAV(String attribute, String appendStr, boolean startOrEnd, SD quote)
524    { return GetSetAttr.appendToAV(this, attribute, appendStr, startOrEnd, quote); }   
525
526
527    // ********************************************************************************************
528    // ********************************************************************************************
529    // Attribute Removal Operations
530    // ********************************************************************************************
531    // ********************************************************************************************
532
533
534    /**
535     * Convenience Method.
536     * <BR />Invokes: {@link #removeAttributes(String[])}
537     */
538    public TagNode remove(String attributeName)
539    {
540        return RemoveAttributes.removeAttributes
541            (this, (String attr) -> ! attr.equalsIgnoreCase(attributeName));
542    }
543
544    /**
545     * <EMBED CLASS='external-html' DATA-FILE-ID=TN_REM_ATTR_DESC>
546     * @param attributes                <EMBED CLASS='external-html' DATA-FILE-ID=TN_REM_ATTR_ATTR>
547     * @return                          <EMBED CLASS='external-html' DATA-FILE-ID=TN_REM_ATTR_RET>
548     * @throws ClosingTagNodeException  <EMBED CLASS='external-html' DATA-FILE-ID=CTNEX>
549     * @see                             ClosingTagNodeException#check(TagNode)
550     */
551    public TagNode removeAttributes(String... attributes)
552    {
553        return RemoveAttributes.removeAttributes
554            (this, (String attr) -> StrCmpr.equalsNAND_CI(attr, attributes));
555    }
556
557    /**
558     * Filter's attributes using an Attribute-<B STYLE='color:red'>Name</B>
559     * {@code String-Predicate}
560     * 
561     * @param attrNameTest Any Java {@code String-Predicate}.  It will be used to test whether or
562     * not to keep or filter/reject an attribute from {@code 'this' TagNode}.
563     * 
564     * <BR /><BR /><B STYLE='color: red;'>NOTE:</B> Like all filter-{@code Predicate's}, this
565     * test's expected behavior is such that it should return {@code TRUE} when it would like to 
566     * keep an attribute having a particular <B STYLE='color: red;'>name</B>, and return
567     * {@code FALSE} when it would like to see the attribute removed from the HTML Tag.
568     * 
569     * @return Removes any Attributes whoe <B STYLE='color: red;'>name</B> as per the rules of the
570     * User-Provided {@code String-Predicate} parameter {@code 'attrNameTest'}.  As with all 
571     * {@code TagNode} modification operations, if any changes are, indeed, made to a new instance 
572     * of {@code TagNode} will be created and returned.
573     */
574    public TagNode removeAttributes(Predicate<String> attrNameTest)
575    { return RemoveAttributes.removeAttributes(this, attrNameTest); }
576
577    /**
578     * <EMBED CLASS='external-html' DATA-FILE-ID=TN_REM_ALL_AV_DESC>
579     * @return                          <EMBED CLASS=external-html DATA-FILE-ID=TN_REM_ALL_AV_RET>
580     * @throws ClosingTagNodeException  <EMBED CLASS=external-html DATA-FILE-ID=CTNEX>
581     * @see                             ClosingTagNodeException#check(TagNode)
582     * @see                             #getInstance(String, TC)
583     * @see                             TC#OpeningTags
584     */
585    public TagNode removeAllAV()
586    {
587        ClosingTagNodeException.check(this);
588
589        // NOTE: We *CANNOT* use the 'tok' field to instantiate the TagNode here, because the 'tok'
590        // String-field is *ALWAYS* guaranteed to be in a lower-case format.  The 'str'
591        // String-field, however uses the original case that was found on the HTML Document by the
592        // parser (or in the Constructor-Parameters that were passed to construct 'this' instance
593        // of TagNode.
594
595        return getInstance(this.str.substring(1, 1 + tok.length()), TC.OpeningTags);
596    }
597
598
599    // ********************************************************************************************
600    // ********************************************************************************************
601    // Retrieve all attributes
602    // ********************************************************************************************
603    // ********************************************************************************************
604
605
606    /**
607     * Convenience Method.
608     * <BR />Invokes: {@link #allAV(boolean, boolean)}
609     * <BR />Attribute-<B STYLE='color: red;'>names</B> will be in lower-case.
610     */
611    public Properties allAV()
612    { return RetrieveAllAttr.allAV(this, false, false);  }
613
614    /**
615     * <EMBED CLASS='external-html' DATA-FILE-ID=TN_ALL_AV_DESC>
616     * @param keepQuotes        <EMBED CLASS='external-html' DATA-FILE-ID=TN_ALL_AV_KQ_PARAM>
617     * @param preserveKeysCase  <EMBED CLASS='external-html' DATA-FILE-ID=TN_ALL_AV_PKC_PARAM>
618     *                          <EMBED CLASS='external-html' DATA-FILE-ID=TAGNODE_PRESERVE_C>
619     * @return                  <EMBED CLASS='external-html' DATA-FILE-ID=TN_ALL_AV_RET>
620     * @see                     StringParse#ifQuotesStripQuotes(String)
621     */
622    public Properties allAV(boolean keepQuotes, boolean preserveKeysCase)
623    { return RetrieveAllAttr.allAV(this, keepQuotes, preserveKeysCase); }
624
625    /**
626     * Convenience Method.
627     * <BR />Invokes: {@link #allAN(boolean, boolean)}
628     * <BR />Attribute-<B STYLE='color: red;'>names</B> will be in lower-case
629     */
630    public Stream<String> allAN()
631    { return RetrieveAllAttr.allAN(this, false, false); }
632
633    /**
634     * This method will only return a list of attribute-<B STYLE='color: red;'>names</B>.  The
635     * attribute-<B STYLE="color: red">values</B> shall <B>NOT</B> be included in the result.  The
636     * {@code String's} returned can have their "case-preserved" by passing {@code TRUE} to the
637     * input boolean parameter {@code 'preserveKeysCase'}.
638     *
639     * @param preserveKeysCase If this is parameter receives {@code TRUE} then the case of the
640     * attribute-<B STYLE='color: red;'>names</B> shall be preserved.
641     *
642     * <EMBED CLASS='external-html' DATA-FILE-ID=TAGNODE_PRESERVE_C>
643     *
644     * @param includeKeyOnlyAttributes When this parameter receives {@code TRUE}, then any
645     * "Boolean Attributes" or "Key-Only, No-Value-Assignment" Inner-Tags will <B>ALSO</B> be
646     * included in the {@code Stream<String>} returned by this method.
647     *
648     * @return an instance of {@code Stream<String>} containing all
649     * attribute-<B STYLE='color: red;'>names</B> identified in {@code 'this'} instance of
650     * {@code TagNode}.  A {@code java.util.stream.Stream} is used because it's contents can easily
651     * be converted to just about any data-type.  
652     *
653     * <EMBED CLASS='external-html' DATA-FILE-ID=STRMCNVT>
654     *
655     * <BR /><B>NOTE:</B> This method shall never return {@code 'null'} - even if there are no 
656     * attribute <B STYLE='color: red;'>key-value</B> pairs contained by {@code 'this' TagNode}.
657     * If there are strictly zero attributes, an empty {@code Stream} shall be returned, instead.
658     * 
659     * @see #allKeyOnlyAttributes(boolean)
660     * @see #allAN()
661     */
662    public Stream<String> allAN(boolean preserveKeysCase, boolean includeKeyOnlyAttributes)
663    { return RetrieveAllAttr.allAN(this, preserveKeysCase, includeKeyOnlyAttributes); }
664
665
666    // ********************************************************************************************
667    // ********************************************************************************************
668    // Key only attributes
669    // ********************************************************************************************
670    // ********************************************************************************************
671
672
673    /**
674     * <EMBED CLASS='external-html' DATA-FILE-ID=TN_ALL_KOA_DESC>
675     * @param preserveKeysCase  <EMBED CLASS='external-html' DATA-FILE-ID=TN_ALL_KOA_PKC>
676     *                          <EMBED CLASS='external-html' DATA-FILE-ID=TAGNODE_PRESERVE_C>
677     * @return                  <EMBED CLASS='external-html' DATA-FILE-ID=TN_ALL_KOA_RET>
678     *                          <EMBED CLASS='external-html' DATA-FILE-ID=STRMCNVT>
679     */
680    public Stream<String> allKeyOnlyAttributes(boolean preserveKeysCase)
681    { return KeyOnlyAttributes.allKeyOnlyAttributes(this, preserveKeysCase); }
682
683    /**
684     * <EMBED CLASS='external-html' DATA-FILE-ID=TN_HAS_KOA_DESC>
685     * @param keyOnlyAttribute          <EMBED CLASS='external-html' DATA-FILE-ID=TN_HAS_KOA_KOA>
686     * @return                          <EMBED CLASS='external-html' DATA-FILE-ID=TN_HAS_KOA_RET>
687     * @throws IllegalArgumentException <EMBED CLASS='external-html' DATA-FILE-ID=TN_HAS_KOA_IAEX>
688     */
689    public boolean hasKeyOnlyAttribute(String keyOnlyAttribute)
690    { return KeyOnlyAttributes.hasKeyOnlyAttribute(this, keyOnlyAttribute); }
691
692
693    // ********************************************************************************************
694    // ********************************************************************************************
695    // testAV
696    // ********************************************************************************************
697    // ********************************************************************************************
698
699
700    /**
701     * Convenience Method.
702     * <BR />Passes: {@code String.equalsIgnoreCase(attributeValue)} to the Test-{@code Predicate}
703     * @see #testAV(String, Predicate)
704     */
705    public boolean testAV(String attributeName, String attributeValue)
706    {
707        return HasAndTest.testAV
708            (this, attributeName, (String s) -> s.equalsIgnoreCase(attributeValue));
709    }
710
711    /**
712     * Convenience Method.
713     * <BR />Passes: {@code attributeValueTest.asPredicate()}
714     * @see #testAV(String, Predicate)
715     */
716    public boolean testAV(String attributeName, Pattern attributeValueTest)
717    { return HasAndTest.testAV(this, attributeName, attributeValueTest.asPredicate()); }
718
719    /**
720     * Convenience Method.
721     * <BR />Passes: {@link TextComparitor#test(String, String[])} to the Test-{@code Predicate}
722     * @see #testAV(String, Predicate)
723     */
724    public boolean testAV
725        (String attributeName, TextComparitor attributeValueTester, String... compareStrs)
726    {
727        return HasAndTest.testAV
728            (this, attributeName, (String s) -> attributeValueTester.test(s, compareStrs));
729    }
730
731    /**
732     * <EMBED CLASS='external-html' DATA-FILE-ID=TN_TEST_AV_DESC>
733     * @param attributeName         <EMBED CLASS='external-html' DATA-FILE-ID=TN_TEST_AV_PARAM>
734     * @param attributeValueTest    Any {@code java.util.function.Predicate<String>}
735     * @return                      <EMBED CLASS='external-html' DATA-FILE-ID=TN_TEST_AV_RET>
736     * @see                         StringParse#ifQuotesStripQuotes(String)
737     */
738    public boolean testAV(String attributeName, Predicate<String> attributeValueTest)
739    { return HasAndTest.testAV(this, attributeName, attributeValueTest); }
740
741
742    // ********************************************************************************************
743    // ********************************************************************************************
744    // has-attribute boolean-logic methods
745    // ********************************************************************************************
746    // ********************************************************************************************
747
748
749    /**
750     * Convenience Method.
751     * <BR />Passes: AND Boolean Logic
752     * <BR />Checks that <B STYLE='color: red;'><I>all</I></B> Attributes are found
753     * @see #hasXOR(boolean, String...)
754     */
755    public boolean hasAND(boolean checkAttributeStringsForErrors, String... attributes)
756    {
757        // First-Function:  Tells the logic to *IGNORE* intermediate matches (returns NULL)
758        //                  (This is *AND*, so wait until all attributes have been found, or at
759        //                  the very least all tags in the element tested, and failed.
760        //
761        // Second-Function: At the End of the Loops, all Attributes have either been found, or
762        //                  at least all attributes in 'this' tag have been tested.  Note that the
763        //                  first-function is only called on a MATCH, and that 'AND' requires to
764        //                  defer a response until all attributes have been tested..  Here, simply
765        //                  RETURN WHETHER OR NOT the MATCH-COUNT equals the number of matches in
766        //                  the user-provided String-array.
767
768        return HasAndTest.hasLogicOp(
769            this,
770            checkAttributeStringsForErrors,
771            (int matchCount) -> null,
772            (int matchCount) -> (matchCount == attributes.length),
773            attributes
774        );
775    }
776
777    /**
778     * Convenience Method.
779     * <BR />Passes: OR Boolean Logic
780     * <BR />Checks that <B STYLE='color: red;'><I>at least one</I></B> of the Attributes match
781     * @see #hasXOR(boolean, String...)
782     */
783    public boolean hasOR(boolean checkAttributeStringsForErrors, String... attributes)
784    {
785        // First-Function:  Tells the logic to return TRUE on any match IMMEDIATELY
786        //
787        // Second-Function: At the End of the Loops, all Attributes have been tested.  SINCE the
788        //                  previous function returns on match immediately, AND SINCE this is an
789        //                  OR, therefore FALSE must be returned (since there were no matches!)
790
791        return HasAndTest.hasLogicOp(
792            this,
793            checkAttributeStringsForErrors,
794            (int matchCount) -> true,
795            (int matchCount) -> false,
796            attributes
797        );
798    }
799
800    /**
801     * Convenience Method.
802     * <BR />Passes: NAND Boolean Logic
803     * <R />Checks that <B STYLE='color: red;'><I>none</I></B> of the Attributes match
804     * @see #hasXOR(boolean, String...)
805     */
806    public boolean hasNAND(boolean checkAttributeStringsForErrors, String... attributes)
807    {
808        // First-Function: Tells the logic to return FALSE on any match IMMEDIATELY
809        //
810        // Second-Function: At the End of the Loops, all Attributes have been tested.  SINCE
811        //                  the previous function returns on match immediately, AND SINCE this is
812        //                  a NAND, therefore TRUE must be returned (since there were no matches!)
813
814        return HasAndTest.hasLogicOp(
815            this,
816            checkAttributeStringsForErrors,
817            (int matchCount) -> false,
818            (int matchCount) -> true,
819            attributes
820        );
821    }
822
823    /**
824     * Convenience Method.
825     * <BR />Passes: XOR Boolean Logic
826     * <BR />Checks that <B STYLE='color: red;'><I>precisely-one</I></B> Attribute is found
827     * <BR /><IMG SRC='doc-files/img/hasAND.png' CLASS=JDIMG ALT=Example>
828     * 
829     * @param checkAttributeStringsForErrors
830     * <EMBED CLASS='external-html' DATA-FILE-ID=TAGNODE_HAS_BOOL>
831     * 
832     * <BR /><BR /><B>NOTE:</B> If this method is passed a zero-length {@code String}-array to the
833     * {@code 'attributes'} parameter, this method shall exit immediately and return {@code FALSE}.
834     * 
835     * @throws InnerTagKeyException If any of the {@code 'attributes'} are not valid HTML
836     * attributes, <I><B>and</B></I> the user has passed {@code TRUE} to parameter
837     * {@code checkAttributeStringsForErrors}.
838     * 
839     * @throws NullPointerException     If any of the {@code 'attributes'} are null.
840     * @throws ClosingTagNodeException  <EMBED CLASS='external-html' DATA-FILE-ID=CTNEX>
841     * @throws IllegalArgumentException If the {@code 'attributes'} parameter has length zero.
842     * @see                             InnerTagKeyException#check(String[])
843     */
844    public boolean hasXOR(boolean checkAttributeStringsForErrors, String... attributes)
845    {
846        // First-Function: Tells the logic to IGNORE the FIRST MATCH, and any matches afterwards
847        //                 should produce a FALSE result immediately
848        //                 (XOR means ==> one-and-only-one)
849        //
850        // Second-Function: At the End of the Loops, all Attributes have been tested.  Just
851        //                  return whether or not the match-count is PRECISELY ONE.
852
853        return HasAndTest.hasLogicOp(
854            this,
855            checkAttributeStringsForErrors,
856            (int matchCount) -> (matchCount == 1) ? null : false,
857            (int matchCount) -> (matchCount == 1),
858            attributes
859        );
860    }
861
862
863    // ********************************************************************************************
864    // ********************************************************************************************
865    // has methods - extended, variable attribute-names
866    // ********************************************************************************************
867    // ********************************************************************************************
868
869
870    /**
871     * Convenience Method.
872     * <BR />Passes: {@code String.equalsIgnoreCase(attributeName)} as the test-{@code Predicate}
873     * @see #has(Predicate)
874     */
875    public boolean has(String attributeName)
876    { return HasAndTest.has(this, (String s) -> s.equalsIgnoreCase(attributeName)); }
877
878    /**
879     * Convenience Method.
880     * <BR />Passes: {@code Pattern.asPredicate()}
881     * @see #has(Predicate)
882     */
883    public boolean has(Pattern attributeNameRegExTest)
884    { return HasAndTest.has(this, attributeNameRegExTest.asPredicate()); }
885
886    /**
887     * Convenience Method.
888     * <BR />Passes: {@link TextComparitor#test(String, String[])} as the test-{@code Predicate}
889     * @see #has(Predicate)
890     */
891    public boolean has(TextComparitor tc, String... compareStrs)
892    { return HasAndTest.has(this, (String s) -> tc.test(s, compareStrs)); }
893
894    /**
895     * <EMBED CLASS='external-html' DATA-FILE-ID=TN_HAS_DESC2>
896     * <EMBED CLASS='external-html' DATA-FILE-ID=TAGNODE_HAS_NOTE>
897     * @param attributeNameTest <EMBED CLASS='external-html' DATA-FILE-ID=TN_HAS_ANT>
898     * @return                  <EMBED CLASS='external-html' DATA-FILE-ID=TN_HAS_RET2>
899     * @see                     StrFilter
900     */
901    public boolean has(Predicate<String> attributeNameTest)
902    { return HasAndTest.has(this, attributeNameTest); }
903
904
905    // ********************************************************************************************
906    // ********************************************************************************************
907    // hasValue(...) methods
908    // ********************************************************************************************
909    // ********************************************************************************************
910
911
912    /**
913     * Convenience Method.
914     * <BR />Passes: {@code String.equals(attributeValue)} as the test-{@code Predicate}
915     * @see #hasValue(Predicate, boolean, boolean)
916     */
917    public Map.Entry<String, String> hasValue
918        (String attributeValue, boolean retainQuotes, boolean preserveKeysCase)
919    {
920        return HasAndTest.hasValue
921            (this, (String s) -> attributeValue.equals(s), retainQuotes, preserveKeysCase);
922    }
923
924    /**
925     * Convenience Method.
926     * <BR />Passes: {@code attributeValueRegExTest.asPredicate()}
927     * @see #hasValue(Predicate, boolean, boolean)
928     */
929    public Map.Entry<String, String> hasValue
930        (Pattern attributeValueRegExTest, boolean retainQuotes, boolean preserveKeysCase)
931    {
932        return HasAndTest.hasValue
933            (this, attributeValueRegExTest.asPredicate(), retainQuotes, preserveKeysCase);
934    }
935
936    /**
937     * Convenience Method.
938     * <BR />Passes: {@link TextComparitor#test(String, String[])} as the test-{@code Predicate}
939     * @see #hasValue(Predicate, boolean, boolean)
940     */
941    public Map.Entry<String, String> hasValue(
942            boolean retainQuotes, boolean preserveKeysCase, TextComparitor attributeValueTester,
943            String... compareStrs
944        )
945    {
946        return HasAndTest.hasValue(
947            this,
948            (String s) -> attributeValueTester.test(s, compareStrs),
949            retainQuotes,
950            preserveKeysCase
951        );
952    }
953
954    /**
955     * <EMBED CLASS='external-html' DATA-FILE-ID=TN_HASVAL_DESC2>
956     * <EMBED CLASS='external-html' DATA-FILE-ID=TN_HASVAL_DNOTE>
957     * @param attributeValueTest    <EMBED CLASS='external-html' DATA-FILE-ID=TN_HASVAL_AVT>
958     * @param retainQuotes          <EMBED CLASS='external-html' DATA-FILE-ID=TN_HASVAL_RQ>
959     * @param preserveKeysCase      <EMBED CLASS='external-html' DATA-FILE-ID=TN_HASVAL_PKC>
960     *                              <EMBED CLASS='external-html' DATA-FILE-ID=TAGNODE_PRESERVE_C>
961     * @return                      <EMBED CLASS='external-html' DATA-FILE-ID=TN_HASVAL_RET2>
962     * @see                         StrFilter
963     */
964    public Map.Entry<String, String> hasValue
965        (Predicate<String> attributeValueTest, boolean retainQuotes, boolean preserveKeysCase)
966    { return HasAndTest.hasValue(this, attributeValueTest, retainQuotes, preserveKeysCase); }
967
968
969    // ********************************************************************************************
970    // ********************************************************************************************
971    // getInstance()
972    // ********************************************************************************************
973    // ********************************************************************************************
974
975
976    /**
977     * <EMBED CLASS='external-html' DATA-FILE-ID=TN_GETINST_DESC>
978     * @param tok Any valid HTML tag.
979     * @param openOrClosed <EMBED CLASS='external-html' DATA-FILE-ID=TN_GETINST_OOC>
980     * @return An instance of this class
981     * 
982     * @throws IllegalArgumentException If parameter {@code TC openOrClose} is {@code null} or
983     * {@code TC.Both}
984     * 
985     * @throws HTMLTokException If the parameter {@code String tok} is not a valid HTML-tag
986     * 
987     * @throws SingletonException If the token requested is a {@code singleton} (self-closing) tag,
988     * but the Tag-Criteria {@code 'TC'} parameter is requesting a closing-version of the tag.
989     * 
990     * @see HTMLTags#hasTag(String, TC)
991     * @see HTMLTags#isSingleton(String)
992     */
993    public static TagNode getInstance(String tok, TC openOrClosed)
994    {
995        if (openOrClosed == null)
996            throw new NullPointerException("The value of openOrClosed cannot be null.");
997
998        if (openOrClosed == TC.Both)
999            throw new IllegalArgumentException("The value of openOrClosed cannot be TC.Both.");
1000
1001        if (HTMLTags.isSingleton(tok) && (openOrClosed == TC.ClosingTags))
1002
1003            throw new SingletonException(
1004                "The value of openOrClosed is TC.ClosingTags, but unfortunately you have asked " +
1005                "for a [" + tok + "] HTML element, which is a singleton element, and therefore " +
1006                "cannot have a closing-tag instance."
1007            );
1008
1009        TagNode ret = HTMLTags.hasTag(tok, openOrClosed);
1010
1011        if (ret == null)
1012            throw new HTMLTokException
1013                ("The HTML-Tag provided isn't valid!\ntok: " + tok + "\nTC: " + openOrClosed);
1014
1015        return ret;
1016    }
1017
1018
1019    // ********************************************************************************************
1020    // ********************************************************************************************
1021    // Methods for "CSS Classes"
1022    // ********************************************************************************************
1023    // ********************************************************************************************
1024
1025
1026    /**
1027     * Convenience Method.
1028     * <BR />Invokes: {@link #cssClasses()}
1029     * <BR />Catches-Exception
1030     */
1031    public Stream<String> cssClassesNOCSE()
1032    { try { return cssClasses(); } catch (CSSStrException e) { return Stream.empty(); } }
1033
1034    /**
1035     * <EMBED CLASS='external-html' DATA-FILE-ID=TN_CSS_CL_DESC>
1036     * @return                  <EMBED CLASS='external-html' DATA-FILE-ID=TN_CSS_CL_RET>
1037     *                          <EMBED CLASS='external-html' DATA-FILE-ID=STRMCNVT>
1038     * @throws CSSStrException  <EMBED CLASS='external-html' DATA-FILE-ID=TN_CSS_CL_CSSSE>
1039     * @see                     #cssClasses()
1040     * @see                     #AV(String)
1041     * @see                     StringParse#WHITE_SPACE_REGEX
1042     * @see                     CSSStrException#check(Stream)
1043     */
1044    public Stream<String> cssClasses()
1045    { return ClassIDStyle.cssClasses(this); }
1046
1047    /**
1048     * <EMBED CLASS='external-html' DATA-FILE-ID=TN_SET_CSS_CL_DESC>
1049     * @param quote             <EMBED CLASS='external-html' DATA-FILE-ID=TGND_QUOTE_EXPL>
1050     * @param appendOrClobber   <EMBED CLASS='external-html' DATA-FILE-ID=TN_SET_CSS_CL_AOC>
1051     * @param cssClasses        <EMBED CLASS='external-html' DATA-FILE-ID=TN_SET_CSS_CL_CCL>
1052     * @return                  <EMBED CLASS='external-html' DATA-FILE-ID=TN_SET_CSS_CL_RET>
1053     * 
1054     * @throws CSSStrException This exception shall throw if any of the {@code 'cssClasses'} in the
1055     * var-args {@code String...} parameter do not meet the HTML 5 CSS {@code Class} naming rules.
1056     * 
1057     * @throws ClosingTagNodeException  <EMBED CLASS=external-html DATA-FILE-ID=CTNEX>
1058     * @throws QuotesException          <EMBED CLASS=external-html DATA-FILE-ID=TN_SET_CSS_CL_QEX>
1059     * @see                             CSSStrException#check(String[])
1060     * @see                             CSSStrException#VALID_CSS_CLASS_OR_NAME_TOKEN
1061     * @see                             #appendToAV(String, String, boolean, SD)
1062     * @see                             #setAV(String, String, SD)
1063     */
1064    public TagNode setCSSClasses(SD quote, boolean appendOrClobber, String... cssClasses)
1065    { return ClassIDStyle.setCSSClasses(this, quote, appendOrClobber, cssClasses); }
1066
1067    /**
1068     * <EMBED CLASS='external-html' DATA-FILE-ID=TN_APD_CSS_CL_DESC>
1069     * @param cssClass This is the CSS-{@code Class} name that is being inserted into
1070     * {@code 'this'} instance of {@code TagNode}
1071     * 
1072     * @param quote                     <EMBED CLASS=external-html DATA-FILE-ID=TGND_QUOTE_EXPL>
1073     * @return                          A new {@code TagNode} with updated CSS {@code Class} Name(s)
1074     * @throws CSSStrException          <EMBED CLASS=external-html DATA-FILE-ID=TN_APD_CSS_CL_CSSSE>
1075     * @throws ClosingTagNodeException  <EMBED CLASS=external-html DATA-FILE-ID=CTNEX>
1076     * @throws QuotesException          <EMBED CLASS=external-html DATA-FILE-ID=TN_APD_CSS_CL_QEX>
1077     * @see                             CSSStrException#check(String[])
1078     * @see                             #setAV(String, String, SD)
1079     * @see                             #appendToAV(String, String, boolean, SD)
1080     */
1081    public TagNode appendCSSClass(String cssClass, SD quote)
1082    { return ClassIDStyle.appendCSSClass(this, cssClass, quote); }
1083
1084
1085    // ********************************************************************************************
1086    // ********************************************************************************************
1087    // Methods for "CSS Style"
1088    // ********************************************************************************************
1089    // ********************************************************************************************
1090
1091
1092    /**
1093     * <EMBED CLASS='external-html' DATA-FILE-ID=TN_CSS_STYLE_DESC>
1094     * <EMBED CLASS='external-html' DATA-FILE-ID=TN_CSS_STYLE_DESCEX>
1095     * @return <EMBED CLASS='external-html' DATA-FILE-ID=TN_CSS_STYLE_RET>
1096     */
1097    public Properties cssStyle()
1098    { return ClassIDStyle.cssStyle(this); }
1099
1100    /**
1101     * <EMBED CLASS='external-html' DATA-FILE-ID=TN_SET_CSS_STY_DESC>
1102     * @param p                         <EMBED CLASS=external-html DATA-FILE-ID=TN_SET_CSS_STY_P>
1103     * @param quote                     <EMBED CLASS=external-html DATA-FILE-ID=TGND_QUOTE_EXPL>
1104     * @param appendOrClobber           <EMBED CLASS=external-html DATA-FILE-ID=TN_SET_CSS_STY_AOC>
1105     * @return                          <EMBED CLASS=external-html DATA-FILE-ID=TN_SET_CSS_STY_RET>
1106     * @throws ClosingTagNodeException  <EMBED CLASS=external-html DATA-FILE-ID=CTNEX>
1107     * @throws CSSStrException          If there is an invalid CSS Style Property Name.
1108     * 
1109     * @throws QuotesException If the style-element's quotation marks are incompatible with any
1110     * and all quotation marks employed by the style-element definitions.
1111     * 
1112     * @see CSSStrException#VALID_CSS_CLASS_OR_NAME_TOKEN
1113     * @see #appendToAV(String, String, boolean, SD)
1114     * @see #setAV(String, String, SD)
1115     */
1116    public TagNode setCSSStyle(Properties p, SD quote, boolean appendOrClobber)
1117    { return ClassIDStyle.setCSSStyle(this, p, quote, appendOrClobber); }
1118
1119
1120    // ********************************************************************************************
1121    // ********************************************************************************************
1122    // Methods for "CSS ID"
1123    // ********************************************************************************************
1124    // ********************************************************************************************
1125
1126
1127    /**
1128     * Convenience Method.
1129     * <BR />Invokes: {@link #AV(String)}
1130     * <BR />Passes: {@code String "id"}, the CSS-ID attribute-<B STYLE='color: red;'>name</B>
1131     */
1132    public String getID()
1133    {
1134        String id = AV("ID");
1135        return (id == null) ? null : id.trim();
1136    }
1137
1138    /**
1139     * This merely sets the current CSS {@code 'ID'} Attribute <B STYLE='color: red;'>Value</B>.
1140     *
1141     * @param id This is the new CSS {@code 'ID'} attribute-<B STYLE='color: red;'>value</B> that
1142     * the user would like applied to {@code 'this'} instance of {@code TagNode}.
1143     * 
1144     * @param quote <EMBED CLASS='external-html' DATA-FILE-ID=TGND_QUOTE_EXPL>
1145     *
1146     * @return Returns a new instance of {@code TagNode} that has an updated {@code 'ID'} 
1147     * attribute-<B STYLE='color: red;'>value</B>.
1148     * 
1149     * @throws IllegalArgumentException This exception shall throw if an invalid
1150     * {@code String}-token has been passed to parameter {@code 'id'}.
1151     *
1152     * <BR /><BR /><B>BYPASS NOTE:</B> If the user would like to bypass this exception-check, for
1153     * instance because he / she is using a CSS Pre-Processor, then applying the general-purpose
1154     * method {@code TagNode.setAV("id", "some-new-id")} ought to suffice.  This other method will
1155     * not apply validity checking, beyond scanning for the usual "quotes-within-quotes" problems,
1156     * which is always disallowed.
1157     *
1158     * @throws ClosingTagNodeException <EMBED CLASS='external-html' DATA-FILE-ID=CTNEX>
1159     * 
1160     * @see CSSStrException#VALID_CSS_CLASS_OR_NAME_TOKEN
1161     * @see #setAV(String, String, SD)
1162     */
1163    public TagNode setID(String id, SD quote)
1164    {
1165        if (! CSSStrException.VALID_CSS_CLASS_OR_NAME_TOKEN_PRED.test(id))
1166
1167            throw new IllegalArgumentException(
1168                "The id parameter provide: [" + id + "], does not conform to the standard CSS " +
1169                "Names.\nEither try using the generic TagNode.setAV(\"id\", yourNewId, quote); " +
1170                "method to bypass this check, or change the value passed to the 'id' parameter " +
1171                "here."
1172            );
1173
1174        return setAV("id", id.trim(), quote);
1175    }
1176
1177
1178    // ********************************************************************************************
1179    // ********************************************************************************************
1180    // Attributes that begin with "data-..."
1181    // ********************************************************************************************
1182    // ********************************************************************************************
1183
1184
1185    /**
1186     * Convenience Method.
1187     * <BR />Invokes: {@link #AV(String)}
1188     * <BR />Passes: {@code "data-"} prepended to parameter {@code 'dataName'} for the
1189     * attribute-<B STYLE='color:red'>name</B>
1190     */
1191    public String dataAV(String dataName)
1192    { return GetSetAttr.AV(this, "data-" + dataName, false); }
1193
1194    /**
1195     * <EMBED CLASS='external-html' DATA-FILE-ID=TN_REM_DATTR_DESC>
1196     * @return                          <EMBED CLASS=external-html DATA-FILE-ID=TN_REM_DATTR_RET>
1197     * @throws ClosingTagNodeException  <EMBED CLASS=external-html DATA-FILE-ID=TN_REM_DATTR_CTNEX>
1198     */
1199    public TagNode removeDataAttributes() 
1200    {
1201        return RemoveAttributes.removeAttributes
1202            (this, (String attr) -> ! StrCmpr.startsWithIgnoreCase(attr, "data-") );
1203    }
1204
1205    /**
1206     * Convenience Method.
1207     * <BR />Invokes: {@link #getDataAV(boolean)}
1208     * <BR />Attribute-<B STYLE='color: red;'>names</B> will be in lower-case
1209     */
1210    public Properties getDataAV() { return getDataAV(false); }
1211
1212    /**
1213     * <EMBED CLASS='external-html' DATA-FILE-ID=TN_GET_DATA_AV_DESC>
1214     * @param preserveKeysCase  <EMBED CLASS='external-html' DATA-FILE-ID=TN_GET_DATA_AV_PAR>
1215     *                          <EMBED CLASS='external-html' DATA-FILE-ID=TAGNODE_PRESERVE_C>
1216     * @return                  <EMBED CLASS='external-html' DATA-FILE-ID=TN_GET_DATA_AV_RET>
1217     */
1218    public Properties getDataAV(boolean preserveKeysCase) 
1219    { return DataAttributes.getDataAV(this, preserveKeysCase); }
1220
1221    /**
1222     * Convenience Method.
1223     * <BR />Invokes: {@link #getDataAN(boolean)}
1224     * <BR />Attribute-<B STYLE='color: red;'>names</B> will be in lower-case
1225     */
1226    public Stream<String> getDataAN() { return getDataAN(false); }
1227
1228    /**
1229     * <EMBED CLASS='external-html' DATA-FILE-ID=TN_GET_DATA_AN_DESC>
1230     * @param preserveKeysCase  <EMBED CLASS='external-html' DATA-FILE-ID=TN_GET_DATA_AN_PAR>
1231     *                          <EMBED CLASS='external-html' DATA-FILE-ID=TAGNODE_PRESERVE_C>
1232     * @return                  <EMBED CLASS='external-html' DATA-FILE-ID=TN_GET_DATA_AN_RET>
1233     *                          <EMBED CLASS='external-html' DATA-FILE-ID=STRMCNVT>
1234     */
1235    public Stream<String> getDataAN(boolean preserveKeysCase) 
1236    { return DataAttributes.getDataAN(this, preserveKeysCase); }
1237
1238
1239    // ********************************************************************************************
1240    // ********************************************************************************************
1241    // Java Methods
1242    // ********************************************************************************************
1243    // ********************************************************************************************
1244
1245
1246    /**
1247     * <EMBED CLASS='external-html' DATA-FILE-ID=TN_TOSTR_AV_DESC>
1248     * @return <EMBED CLASS='external-html' DATA-FILE-ID=TN_TOSTR_AV_RET>
1249     * @see HTMLNode#toString()
1250     */
1251    public String toStringAV()
1252    { return GeneralPurpose.toStringAV(this); }
1253
1254    /**
1255     * Java's {@code interface Cloneable} requirements.  This instantiates a new {@code TagNode}
1256     * with identical <SPAN STYLE='color: red;'>{@code String str}</SPAN> fields, and also
1257     * identical <SPAN STYLE='color: red;'>{@code boolean isClosing}</SPAN> and
1258     * <SPAN STYLE='color: red;'>{@code String tok}</SPAN> fields.
1259     * 
1260     * @return A new {@code TagNode} whose internal fields are identical to this one.
1261     */
1262    public TagNode clone() { return new TagNode(str); }
1263
1264    /**
1265     * <EMBED CLASS='external-html' DATA-FILE-ID=TN_COMPARETO_DESC>
1266     * @param n Any other {@code TagNode} to be compared to {@code 'this' TagNode}
1267     * @return An integer that fulfils Java's {@code Comparable} interface-method requirements.
1268     */
1269    public int compareTo(TagNode n)
1270    {
1271        // Utilize the standard "String.compare(String)" method with the '.tok' string field.
1272        // All 'tok' fields are stored as lower-case strings.
1273        int compare1 = this.tok.compareTo(n.tok);
1274
1275        // Comparison #1 will be non-zero if the two TagNode's being compared had different
1276        // .tok fields
1277        if (compare1 != 0) return compare1;
1278
1279        // If the '.tok' fields were the same, use the 'isClosing' field for comparison instead.
1280        // This comparison will only be used if they are different.
1281        if (this.isClosing != n.isClosing) return (this.isClosing == false) ? -1 : 1;
1282    
1283        // Finally try using the entire element '.str' String field, instead.  
1284        return this.str.length() - n.str.length();
1285    }
1286
1287
1288    // ********************************************************************************************
1289    // ********************************************************************************************
1290    // toUpperCase 
1291    // ********************************************************************************************
1292    // ********************************************************************************************
1293
1294
1295    /**
1296     * <EMBED CLASS=defs DATA-CASE=Upper DATA-CAPITAL="">
1297     * <EMBED CLASS='external-html' DATA-FILE-ID=TN_TO_UPLOW_1_DESC>
1298     * @param justTag_Or_TagAndAttributeNames 
1299     * <EMBED CLASS='external-html' DATA-FILE-ID=TN_TO_UPLOW_1_PARAM>
1300     * @return <EMBED CLASS='external-html' DATA-FILE-ID=TN_TO_UPLOW_1_RET>
1301     */
1302    public TagNode toUpperCase(boolean justTag_Or_TagAndAttributeNames)
1303    {
1304        return CaseChange.toCaseInternal
1305            (this, justTag_Or_TagAndAttributeNames, String::toUpperCase);
1306    }
1307
1308    /**
1309     * Convenience Method.
1310     * <BR />Passes: {@code StrCmpr.equalsXOR_CI(attrName, attributeNames)}
1311     * @see #toUpperCase(boolean, Predicate)
1312     * @see StrCmpr#equalsXOR_CI(String, String...)
1313     */
1314    public TagNode toUpperCase(boolean tag, String... attributeNames)
1315    {
1316        return CaseChange.toCaseInternal(
1317            this, tag,
1318            (String attrName) -> StrCmpr.equalsXOR_CI(attrName, attributeNames),
1319            String::toUpperCase
1320        );
1321    }
1322
1323    /**
1324     * <EMBED CLASS=defs DATA-CASE=Upper DATA-CAPITAL="">
1325     * <EMBED CLASS='external-html' DATA-FILE-ID=TN_TO_UPLOW_2_DESC>
1326     * @param tag           Indicates whether or not the Tag-Name should be capitalized
1327     * @param attrNameTest  <EMBED CLASS='external-html' DATA-FILE-ID=TN_TO_UPLOW_2_PARAM>
1328     * @return              <EMBED CLASS='external-html' DATA-FILE-ID=TN_TO_UPLOW_2_RET>
1329     */
1330    public TagNode toUpperCase(boolean tag, Predicate<String> attrNameTest)
1331    { return CaseChange.toCaseInternal(this, tag, attrNameTest, String::toUpperCase); }
1332
1333
1334    // ********************************************************************************************
1335    // ********************************************************************************************
1336    // toLowerCase 
1337    // ********************************************************************************************
1338    // ********************************************************************************************
1339
1340
1341    /**
1342     * <EMBED CLASS=defs DATA-CASE=Lower DATA-CAPITAL="de-">
1343     * <EMBED CLASS='external-html' DATA-FILE-ID=TN_TO_UPLOW_1_DESC>
1344     * @param justTag_Or_TagAndAttributeNames 
1345     * <EMBED CLASS='external-html' DATA-FILE-ID=TN_TO_UPLOW_1_PARAM>
1346     * @return <EMBED CLASS='external-html' DATA-FILE-ID=TN_TO_UPLOW_1_RET>
1347     */
1348    public TagNode toLowerCase(boolean justTag_Or_TagAndAttributeNames)
1349    {
1350        return CaseChange.toCaseInternal
1351            (this, justTag_Or_TagAndAttributeNames, String::toLowerCase);
1352    }
1353
1354    /**
1355     * Convenience Method.
1356     * <BR />Passes: {@code StrCmpr.equalsXOR_CI(attrName, attributeNames)}
1357     * @see #toLowerCase(boolean, Predicate)
1358     * @see StrCmpr#equalsXOR_CI(String, String...)
1359     */
1360    public TagNode toLowerCase(boolean tag, String... attributeNames)
1361    {
1362        return CaseChange.toCaseInternal(
1363            this, tag,
1364            (String attrName) -> StrCmpr.equalsXOR_CI(attrName, attributeNames),
1365            String::toLowerCase
1366        );
1367    }
1368
1369    /**
1370     * <EMBED CLASS=defs DATA-CASE=Lower DATA-CAPITAL="de-">
1371     * <EMBED CLASS='external-html' DATA-FILE-ID=TN_TO_UPLOW_2_DESC>
1372     * @param tag           Indicates whether or not the Tag-Name should be decapitalized
1373     * @param attrNameTest  <EMBED CLASS='external-html' DATA-FILE-ID=TN_TO_UPLOW_2_PARAM>
1374     * @return              <EMBED CLASS='external-html' DATA-FILE-ID=TN_TO_UPLOW_2_RET>
1375     */
1376    public TagNode toLowerCase(boolean tag, Predicate<String> attrNameTest)
1377    { return CaseChange.toCaseInternal(this, tag, attrNameTest, String::toLowerCase); }
1378
1379
1380    // ********************************************************************************************
1381    // ********************************************************************************************
1382    // Attribute-Value Quotation-Marks - REMOVE
1383    // ********************************************************************************************
1384    // ********************************************************************************************
1385
1386
1387    /**
1388     * Convenience Method.
1389     * <BR />Removes Quotation-Marks from <B STYLE='color: red;'>Value</B> whose Inner-Tag 
1390     * <B STYLE='color: red;'>Name</B> matches {@code 'attributeName'}
1391     * @see removeAVQuotes(Predicate)
1392     */
1393    public TagNode removeAVQuotes(String attributeName)
1394    {
1395        return QuotationMarks.removeAVQuotes
1396            (this, (String attr) -> attr.equalsIgnoreCase(attributeName));
1397    }
1398
1399    /**
1400     * Convenience Method.
1401     * <BR />Removes Quotation-Marks from all Inner-Tag <B STYLE='color: red;'>Values</B>
1402     * @see removeAVQuotes(Predicate)
1403     */
1404    public TagNode removeAllAVQuotes()
1405    { return QuotationMarks.removeAVQuotes(this, (String attr) -> true); }
1406
1407    /**
1408     * <EMBED CLASS='external-html' DATA-FILE-ID=TN_REM_QUOTES_DESC>
1409     * @param attrNameTest      <EMBED CLASS='external-html' DATA-FILE-ID=TN_REM_QUOTES_PARAM>
1410     * @return                  <EMBED CLASS='external-html' DATA-FILE-ID=TN_REM_QUOTES_RET>
1411     * @throws QuotesException  If the resulting <CODE>TagNode</CODE> contains Quotation-Errors
1412     */
1413    public TagNode removeAVQuotes(Predicate<String> attrNameTest)
1414    { return QuotationMarks.removeAVQuotes(this, attrNameTest); }
1415
1416
1417    // ********************************************************************************************
1418    // ********************************************************************************************
1419    // Attribute-Value Quotation-Marks - SET
1420    // ********************************************************************************************
1421    // ********************************************************************************************
1422
1423
1424    /**
1425     * Convenience Method.
1426     * <BR />Set Quotation-Marks from <B STYLE='color: red;'>Value</B> whose Inner-Tag 
1427     * <B STYLE='color: red;'>Name</B> matches {@code 'attributeName'}
1428     * @see setAVQuotes(Predicate, SD)
1429     */
1430    public TagNode setAVQuotes(String attributeName, SD quote)
1431    {
1432        return QuotationMarks.setAVQuotes
1433            (this, (String attr) -> attr.equalsIgnoreCase(attributeName), quote);
1434    }
1435
1436    /**
1437     * Convenience Method.
1438     * <BR />Set the Quotation-Marks for all Inner-Tag <B STYLE='color: red;'>Values</B>
1439     * @see setAVQuotes(Predicate, SD)
1440     */
1441    public TagNode setAllAVQuotes(SD quote)
1442    { return QuotationMarks.setAVQuotes(this, (String attr) -> true, quote); }
1443
1444    /**
1445     * <EMBED CLASS='external-html' DATA-FILE-ID=TN_SET_QUOTES_DESC>
1446     * @param attrNameTest          <EMBED CLASS='external-html' DATA-FILE-ID=TN_SET_QUOTES_PARAM>
1447     * @param quote                 The new Quotation-Mark to apply
1448     * @return                      <EMBED CLASS='external-html' DATA-FILE-ID=TN_SET_QUOTES_RET>
1449     * @throws QuotesException      If the resulting <CODE>TagNode</CODE> contains Quotation-Errors
1450     * @throws NullPointerException If either parameter is passed null.
1451     */
1452    public TagNode setAVQuotes(Predicate<String> attrNameTest, SD quote)
1453    { return QuotationMarks.setAVQuotes(this, attrNameTest, quote); }
1454
1455
1456    // ********************************************************************************************
1457    // ********************************************************************************************
1458    // Attribute-Value Quotation-Marks - GET
1459    // ********************************************************************************************
1460    // ********************************************************************************************
1461
1462
1463    // TO-DO: First, I am going to "Modernize" the get/set AV methods
1464    // Then this will work
1465}