1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
package Torello.HTML;

import Torello.HTML.NodeSearch.*;
import Torello.Java.FileRW; // used in @see comments
import Torello.Java.StringParse;
import Torello.Java.Additional.Ret2;

import java.util.*;
import java.util.function.BiConsumer;
import java.util.stream.IntStream;

/**
 * Utilities for checking that opening and closing {@link TagNode} elements match up (that the HTML
 * is balanced).
 * 
 * <EMBED CLASS='external-html' DATA-FILE-ID=BALANCE>
 */
@Torello.JavaDoc.Annotations.StaticFunctional
public class Balance
{
    private Balance() { }

    /**
     * <EMBED CLASS='external-html' DATA-FILE-ID=B_CB_DESC>
     * @param html <EMBED CLASS='external-html' DATA-FILE-ID=HTMLVECSUP>
     * 
     * @return Will return null if the snippet or page has 'balanced' HTML, otherwise returns the
     * trimmed balance-report as a {@code String}.
     */
    public static String CB(final Vector<HTMLNode> html)
    {
        final String ret = toStringBalance(checkNonZero(check(html)));
        return (ret.length() == 0) ? null : ret;
    }

    /**
     * <EMBED CLASS='external-html' DATA-FILE-ID=B_CHECK_V1_DESC_P1>
     * <EMBED CLASS='external-html' DATA-FILE-ID=BALANCE_VALID_NOTE1>
     * <EMBED CLASS='external-html' DATA-FILE-ID=B_CHECK_V1_DESC_P2>
     * @param html  <EMBED CLASS='external-html' DATA-FILE-ID=HTMLVECSUP>
     * @return      <EMBED CLASS='external-html' DATA-FILE-ID=B_CHECK_V1_RET>
     * @see         FileRW#loadFileToString(String)
     * @see         HTMLPage#getPageTokens(CharSequence, boolean)
     */
    public static Hashtable<String, Integer> check(final Vector<? super TagNode> html)
    {
        final Hashtable<String, Integer> ht = new Hashtable<>();

        // Iterate through the HTML List, we are only counting HTML Elements, not text or comments
        for (final Object o : html) if (o instanceof TagNode)
        {
            final TagNode tn = (TagNode) o;

            // Singleton tags are also known as 'self-closing' tags.  BR, HR, IMG, etc...
            if (HTMLTags.isSingleton(tn.tok)) continue;

    
            // Current value in the table, or 'null' if this tag hasn't been seen yet.
            // 
            // An opening-version (TC.OpeningTags, For Instance <DIV ...>) will ADD 1 to the count
            // A closing-tag (For Instance: </DIV>) will SUBTRACT 1 from the count

            final Integer I = ht.get(tn.tok);

            final int updated =
                ((I == null) ? 0 : I) +     // Convert 'null' to Zero; otherwise no-change
                (tn.isClosing ? -1 : 1);    // ClosingTags => -1, OpeningTags => +1

            // Update the return result Hashtable for this particular HTML-Element (tn.tok)
            ht.put(tn.tok, updated);
        }

        return ht;
    }

    /**
     * Creates an array that includes an open-and-close {@code 'count'} for each HTML-Tag / 
     * that was requested via the passed input {@code String[]}-Array parameter {@code 'htmlTags'}.
     * 
     * <EMBED CLASS='external-html' DATA-FILE-ID=BALANCE_VALID_NOTE1> <!-- Validity Note -->
     * 
     * @param html              <EMBED CLASS='external-html' DATA-FILE-ID=HTMLVECSUP>
     * @param htmlTags          <EMBED CLASS='external-html' DATA-FILE-ID=B_CHECK_V2_HTMLTAGS>
     * @return                  <EMBED CLASS='external-html' DATA-FILE-ID=B_CHECK_V2_RET>
     * @throws HTMLTokException If any of the tags passed are not valid HTML tags.
     * 
     * @throws SingletonException If any of the {@code String}-Tags passed to parameter
     * {@code 'htmlTags'} are {@code 'singleton'} (Self-Closing) Tags, then this exception throws
     */
    public static int[] check(final Vector<? super TagNode> html, String... htmlTags)
    {
        // Check that these are all valid HTML Tags, throw an exception if not.
        htmlTags = ARGCHECK.htmlTags(htmlTags);

        // Temporary Hash-table, used to store the count of each htmlTag
        final Hashtable<String, Integer> ht = new Hashtable<>();


        // Initialize the temporary hash-table.  This will be discarded at the end of the method,
        // and converted into a parallel array.  (Parallel to the input String... htmlTags array).
        // Also, check to make sure the user hasn't requested a count of Singleton HTML Elements.

        for (final String htmlTag : htmlTags)
        {
            if (HTMLTags.isSingleton(htmlTag)) throw new SingletonException(
                "One of the tags you have passed: [" + htmlTag + "] is a singleton-tag, " +
                "and is only allowed opening versions of the tag."
            );

            ht.put(htmlTag, Integer.valueOf(0));
        }

        // Iterate through the HTML List, we are only counting HTML Elements, not text or comments
        for (final Object o : html) if (o instanceof TagNode)
        {
            final TagNode tn = (TagNode) o;

            // Get the current count from the hash-table
            final Integer I = ht.get(tn.tok);

            // The hash-table only holds elements we are counting, if null, then skip.
            if (I == null) continue;


            // Save the new, computed count, in the hash-table
            //
            // An opening-version (TC.OpeningTags, For Instance <DIV ...>) will ADD 1 to the count
            // A closing-tag (For Instance: </DIV>) will SUBTRACT 1 from the count
            //
            // NOTE: this line of code utilizes Java's Auto-Boxing & Auto-Unboxing features.

            ht.put(tn.tok, I + (tn.isClosing ? -1 : 1));
        }


        // Convert the hash-table to an integer-array, and return this to the user
        // Chat-GPT has assured me, by the way, that arrays are always initialized with zeroes.
        // 
        // No need to set the elements of this array to zero...  Ok... I new that.  😊😊

        final int[] ret = new int[htmlTags.length]; 

        for (int i=0; i < htmlTags.length; i++)
        {
            final Integer I = ht.get(htmlTags[i]);

            // The assignment part of this 'if' statement is Java's Auto Un-Boxing feature
            if (I != null) ret[i] = I;
        }

        return ret;
    }

    /**
     * <EMBED CLASS='external-html' DATA-FILE-ID=B_CHECK_NZ_DESC>
     * <EMBED CLASS='external-html' DATA-FILE-ID=BALANCE_VALID_NOTE1>   <!-- Validity Note  -->
     * <EMBED CLASS='external-html' DATA-FILE-ID=BALANCE_CLONE>         <!-- Clone Note     -->
     *
     * @param ht This should be a {@code Hashtable} that was produced by a call to one of the two
     * available {@code check(...)} methods.
     * 
     * @return <EMBED CLASS='external-html' DATA-FILE-ID=B_CHECK_NZ_RET>
     */
    public static Hashtable<String, Integer> checkNonZero(final Hashtable<String, Integer> ht)
    {
        @SuppressWarnings("unchecked")
        final Hashtable<String, Integer>    ret     = (Hashtable<String, Integer>) ht.clone();
        final Enumeration<String>           keys    = ret.keys();

        while (keys.hasMoreElements())
        {
            final String key = keys.nextElement();

            // Remove any keys (HTML element-names) that have a normal ('0') count.
            if (ret.get(key).intValue() == 0) ret.remove(key);
        }

        return ret;
    }


    /**
     * This will compute a {@code count} for just one, particular, HTML Element of whether that
     * Element has been properly opened and closed.  An open and close {@code count} (integer
     * value) will be returned by this method.
     * 
     * <EMBED CLASS='external-html' DATA-FILE-ID=BALANCE_VALID_NOTE1> <!-- Validity Note -->
     * 
     * @param html <EMBED CLASS='external-html' DATA-FILE-ID=HTMLVECSUP>
     * 
     * @param htmlTag This the html element whose open-close count needs to be kept.
     * 
     * @return The count of each html-element present in this {@code Vector}.  For instance, if the
     * user had requested that HTML Anchor Links be counted, and if the input {@code Vector} had 5
     * {@code '<A ...>'} (Anchor-Link) elements, and six {@code '</A>'} then this method would
     * return {@code -1}.
     * 
     * @throws HTMLTokException If any of the tags passed are not valid HTML tags.
     * 
     * @throws SingletonException If this {@code 'htmlTag'} is a {@code 'singleton'} (Self-Closing)
     * Tag, this exception will throw.
     */
    public static int checkTag(final Vector<? super TagNode> html, String htmlTag)
    {
        // Check that this is a valid HTML Tag, throw an exception if invalid
        htmlTag = ARGCHECK.htmlTag(htmlTag);

        if (HTMLTags.isSingleton(htmlTag)) throw new SingletonException(
            "The tag you have passed: [" + htmlTag + "] is a singleton-tag, and is only " +
            "allowed opening versions of the tag."
        );


        // Iterate through the HTML List, we are only counting HTML Elements, not text, and
        // not HTML Comments

        TagNode tn;
        int i = 0;

        for (final Object o : html) if (o instanceof TagNode) 

            // If we encounter an HTML Element whose tag is the tag whose count we are 
            // computing, then....

            if ((tn = (TagNode) o).tok.equals(htmlTag))
            
                // An opening-version (TC.OpeningTags, For Instance <DIV ...>) will ADD 1 to the count
                // A closing-tag (For Instance: </DIV>) will SUBTRACT 1 from the count

                i += tn.isClosing ? -1 : 1;

        return i;
    }


    /**
     * <EMBED CLASS='external-html' DATA-FILE-ID=B_DEPTH_V1_DESC_P1>
     * <EMBED CLASS='external-html' DATA-FILE-ID=BALANCE_VALID_NOTE2>
     * <EMBED CLASS='external-html' DATA-FILE-ID=B_DEPTH_V1_DESC_P2>
     *
     * @param html  <EMBED CLASS='external-html' DATA-FILE-ID=HTMLVECSUP>
     * @return      <EMBED CLASS='external-html' DATA-FILE-ID=B_DEPTH_V1_RET>
     * 
     * @throws HTMLTokException     If any of the tags passed are not valid HTML tags.
     * @throws SingletonException   throws if {@code 'htmlTag'} is a 'singleton' (Self-Closing) tag
     */
    public static Hashtable<String, int[]> depth(final Vector<? super TagNode> html)
    {
        final Hashtable<String, int[]> ht = new Hashtable<>();

        // Iterate through the HTML List, we are only counting HTML Elements, not text, and not HTML Comments
        for (Object o : html) if (o instanceof TagNode) 
        {
            final TagNode tn = (TagNode) o;

            // Don't keep a count on singleton tags.
            if (HTMLTags.isSingleton(tn.tok)) continue;


            // If this is the first encounter of a particular HTML Element, create a MAX/MIN
            // integer array, and initialize it's values to zero.

            int[] curMaxAndMinArr = ht.get(tn.tok);

            if (curMaxAndMinArr == null)

                // Current Min Depth Count for Element "tn.tok" is zero
                // Current Max Depth Count for Element "tn.tok" is zero
                // Current Computed Depth Count for "tn.tok" is zero

                ht.put(tn.tok, curMaxAndMinArr = new int[3]);


            // curCount += tn.isClosing ? -1 : 1;
            //
            // An opening-version (TC.OpeningTags, For Instance <DIV ...>) will ADD 1 to the count
            // A closing-tag (For Instance: </DIV>) will SUBTRACT 1 from the count

            curMaxAndMinArr[2] += tn.isClosing ? -1 : 1;


            // If the current depth-count is a "New Minimum" (a new low! :), then save it in the
            // minimum pos of the output-array.

            if (curMaxAndMinArr[2] < curMaxAndMinArr[0])
                curMaxAndMinArr[0] = curMaxAndMinArr[2];


            // If the current depth-count (for this tag) is a "New Maximum" (a new high), save it
            // to the max-pos of the output-array.

            if (curMaxAndMinArr[2] > curMaxAndMinArr[1])
                curMaxAndMinArr[1] = curMaxAndMinArr[2];
        }

        return ht;
    }


    /**
     * <EMBED CLASS='external-html' DATA-FILE-ID=B_DEPTH_V2_DESC_P1>
     * <EMBED CLASS='external-html' DATA-FILE-ID=BALANCE_VALID_NOTE2>
     * <EMBED CLASS='external-html' DATA-FILE-ID=B_DEPTH_V2_DESC_P2>
     * 
     * @param html              <EMBED CLASS='external-html' DATA-FILE-ID=HTMLVECSUP>
     * @return                  <EMBED CLASS='external-html' DATA-FILE-ID=B_DEPTH_V2_RET>
     * @throws HTMLTokException If any of the tags passed are not valid HTML tags.
     * 
     * @throws SingletonException If this {@code 'htmlTag'} is a {@code 'singleton'}
     * (Self-Closing) Tag, this exception will throw.
     */
    public static Hashtable<String, int[]> depth(
            final Vector<? super TagNode>   html,
            String...                       htmlTags
        )
    {
        // Check that these are all valid HTML Tags, throw an exception if not.
        htmlTags = ARGCHECK.htmlTags(htmlTags);

        final Hashtable<String, int[]> ht = new Hashtable<>();


        // Initialize the temporary hash-table.  This will be discarded at the end of the method,
        // and converted into a parallel array.  (Parallel to the input String... htmlTags array).
        // Also, check to make sure the user hasn't requested a count of Singleton HTML Elements.

        for (final String htmlTag : htmlTags)

            if (HTMLTags.isSingleton(htmlTag)) throw new SingletonException(
                "One of the tags you have passed: [" + htmlTag + "] is a singleton-tag, " +
                "and is only allowed opening versions of the tag."
            );

            // Insert a new array for this HTML-Tag,  Java Auto Initializes array cells to zero
            else ht.put(htmlTag, new int[3]);


        // Iterate through the HTML List, we are only counting HTML Elements, not text nor comments
        for (final Object o: html) if (o instanceof TagNode) 
        {
            final TagNode tn = (TagNode) o;

            final int[] curMaxAndMinArr = ht.get(tn.tok);


            // If this is null, we are attempting to perform the count on an HTML Element that
            // wasn't requested by the user with the var-args 'String... htmlTags' parameter.
            // The Hashtable was initialized to only have those tags. (see about 5 lines above 
            // where the Hashtable is initialized)

            if (curMaxAndMinArr == null) continue;


            // An opening-version (TC.OpeningTags, For Instance <DIV ...>) will ADD 1 to the count
            // A closing-tag (For Instance: </DIV>) will SUBTRACT 1 from the count

            curMaxAndMinArr[2] += tn.isClosing ? -1 : 1;

    
            // If the current depth-count is a "New Minimum" (a new low! :), then save it in the
            // minimum pos of the output-array.

            if (curMaxAndMinArr[2] < curMaxAndMinArr[0]) curMaxAndMinArr[0] = curMaxAndMinArr[2];


            // If the current depth-count (for this tag) is a "New Maximum" (a new high), save it
            // to the max-pos of the output-array.

            if (curMaxAndMinArr[2] > curMaxAndMinArr[1]) curMaxAndMinArr[1] = curMaxAndMinArr[2];


            // No need to update the hash-table, since this is an array - changing its
            // values is already "reflected" into the Hashtable.
        }

        return ht;
    }


    /**
     * <EMBED CLASS='external-html' DATA-FILE-ID=B_DEPTH_INVAL_DESC>
     * 
     * @param ht This should be a {@code Hashtable} that was produced by a call to one of the two
     * available {@code depth(...)} methods.
     * 
     * @return This shall a return a list of HTML Tags that are <I>potentially (but not guaranteed
     * to be)</I> invalid.
     */
    public static Hashtable<String, int[]> depthInvalid(final Hashtable<String, int[]> ht)
    {
        @SuppressWarnings("unchecked")
        final Hashtable<String, int[]>  ret     = (Hashtable<String, int[]>) ht.clone();
        final Enumeration<String>       keys    = ret.keys();


        // Using the "Enumeration" class allows the situation where elements can be removed from
        // the underlying data-structure - while iterating through that data-structure.  This is
        // not possible using a keySet Iterator.

        while (keys.hasMoreElements())
        {
            final String    key = keys.nextElement();
            final int[]     arr = ret.get(key);

            if ((arr[1] >= 0) && (arr[2] == 0)) ret.remove(key);
        }

        return ret;
    }

    /**
     * <EMBED CLASS='external-html' DATA-FILE-ID=B_DEPTH_GT1_DESC>
     *
     * @param ht This should be a {@code Hashtable} that was produced by a call to one of the two
     * available {@code depth(...)} methods.
     * 
     * @return This shall a return a list of HTML Tags that are <I>potentially (but not guaranteed
     * to be)</I> invalid.
     */
    public static Hashtable<String, int[]> depthGreaterThanOne(final Hashtable<String, int[]> ht)
    {
        @SuppressWarnings("unchecked")
        final Hashtable<String, int[]>  ret     = (Hashtable<String, int[]>) ht.clone();
        final Enumeration<String>       keys    = ret.keys();


        // Using the "Enumeration" class allows the situation where elements can be removed from
        // the underlying data-structure - while iterating through that data-structure.  This is not
        // possible using a keySet Iterator.

        while (keys.hasMoreElements())
        {
            final String    key = keys.nextElement();
            final int[]     arr = ret.get(key);

            if (arr[1] == 1) ret.remove(key);
        }

        return ret;
    }


    /**
     * <EMBED CLASS='external-html' DATA-FILE-ID=B_DEPTH_TAG_DESC_P1>
     * <EMBED CLASS='external-html' DATA-FILE-ID=BALANCE_VALID_NOTE2>
     * <EMBED CLASS='external-html' DATA-FILE-ID=B_DEPTH_TAG_DESC_P2>
     *
     * @param html      <EMBED CLASS='external-html' DATA-FILE-ID=HTMLVECSUP>
     * @param htmlTag   The html element whose maximum and minimum depth-count needs to be computed
     * @return          <EMBED CLASS='external-html' DATA-FILE-ID=B_DEPTH_TAG_RET>
     * 
     * @throws HTMLTokException     throws if any of the tags passed are not valid HTML tags
     * @throws SingletonException   throws if {@code 'htmlTag'} is a 'singleton' (Self-Closing) tag
     */
    public static int[] depthTag(final Vector<? super TagNode> html, String htmlTag)
    {
        // Check that this is a valid HTML Tag, throw an exception if invalid
        htmlTag = ARGCHECK.htmlTag(htmlTag);

        if (HTMLTags.isSingleton(htmlTag)) throw new SingletonException(
            "The tag you have passed: [" + htmlTag + "] is a singleton-tag, and is only allowed " +
            "opening versions of the tag."
        );

        int i = 0, max = 0, min = 0;

        // Iterate through the HTML List, we are only counting HTML Elements, not text or Comments
        for (final Object o : html) if (o instanceof TagNode)
        {
            final TagNode tn = (TagNode) o;
            if (! tn.tok.equals(htmlTag)) continue;

            // An opening "<TABLE ...>" ADDS 1 to the count. A closing-"</TABLE>" SUBTRACTS 1
            i += tn.isClosing ? -1 : 1;

            if (i > max) max = i;
            if (i < min) min = i;
        }

        return new int[] { min, max, i };
    }

    /**
     * <EMBED CLASS='external-html' DATA-FILE-ID=B_NON_NESTED_C_DESC>
     * @param html      <EMBED CLASS='external-html' DATA-FILE-ID=HTMLVECSUP>
     * @param htmlTag   <EMBED CLASS='external-html' DATA-FILE-ID=B_NON_NESTED_C_HT>
     * @return          <EMBED CLASS='external-html' DATA-FILE-ID=B_NON_NESTED_C_RET>
     * @throws HTMLTokException If any of the tags passed are not valid HTML tags.
     * 
     * @throws SingletonException If this {@code 'htmlTag'} is a {@code 'singleton'} (Self-Closing)
     * Tag, this exception will throw.
     * 
     * @see FileRW#loadFileToString(String)
     * @see HTMLPage#getPageTokens(CharSequence, boolean)
     * @see Debug#print(Vector, int[], int, String, boolean, BiConsumer)
     */
    public static int[] nonNestedCheck(final Vector<? super TagNode> html, String htmlTag)
    {
        // Check that this is a valid HTML Tag, throw an exception if invalid
        htmlTag = ARGCHECK.htmlTag(htmlTag);

        if (HTMLTags.isSingleton(htmlTag)) throw new SingletonException(
            "The tag you have passed: [" + htmlTag + "] is a singleton-tag, and is only " +
            "allowed opening versions of the tag."
        );


        // Java Streams are an easier way to keep variable-length lists.  They use "builders".
        // This one is for an "IntStream"

        final IntStream.Builder b = IntStream.builder();

        // Iterate through  HTML List, we are only counting HTML Elements, not text nor commeents
        final int LEN = html.size();
        TC last = null;

        for (int i=0; i < LEN; i++)

            if (html.elementAt(i) instanceof TagNode)
            {
                final TagNode tn = (TagNode) html.elementAt(i);
                if (! tn.tok.equals(htmlTag)) continue;

                if ((tn.isClosing)      && (last == TC.ClosingTags)) b.add(i);
                if ((! tn.isClosing)    && (last == TC.OpeningTags)) b.add(i);

                last = tn.isClosing ? TC.ClosingTags : TC.OpeningTags;
            }

        return b.build().toArray();
    }

    /**
     * <EMBED CLASS='external-html' DATA-FILE-ID=B_LOC_DEPTH_DESC>
     * @param html      <EMBED CLASS='external-html' DATA-FILE-ID=HTMLVECSUP>
     * @param htmlTag   This the html element that has an imbalanced OPEN-CLOSE ratio in the tree.
     * @return          <EMBED CLASS='external-html' DATA-FILE-ID=B_LOC_DEPTH_RET>
     * 
     * @throws HTMLTokException     throws if any of the tags passed are not valid HTML tags.
     * @throws SingletonException   throws if {@code 'htmlTag'} is a 'singleton' - Self-Closing Tag
     */
    public static Ret2<int[], int[]> locationsAndDepth
        (final Vector<? super TagNode> html, String htmlTag)
    {
        // Check that this is a valid HTML Tag, throw an exception if invalid
        htmlTag = ARGCHECK.htmlTag(htmlTag);

        if (HTMLTags.isSingleton(htmlTag)) throw new SingletonException(
            "The tag you have passed: [" + htmlTag + "] is a singleton-tag, and is only " +
            "allowed opening versions of the tag."
        );


        // Java Streams are an easier way to keep variable-length lists.  They use "builders".
        // These builders are for an "IntStream"

        final IntStream.Builder locations       = IntStream.builder();
        final IntStream.Builder depthAtLocation = IntStream.builder();

        // Iterate through the HTML List, we are only counting HTML Elements, not text or comments
        final int LEN = html.size();
        int depth = 0;

        for (int i=0; i < LEN; i++)

            if (html.elementAt(i) instanceof TagNode) 
            {
                final TagNode tn = (TagNode) html.elementAt(i);

                if (! tn.tok.equals(htmlTag)) continue;

                depth += tn.isClosing ? -1 : 1;
                locations.add(i);
                depthAtLocation.add(depth);
            }

        return new Ret2<int[], int[]>
            (locations.build().toArray(), depthAtLocation.build().toArray());
    }

    /**
     * Converts a depth report to a {@code String}, for printing.
     * @param depthReport This should be a {@code Hashtable} returned by any of the depth-methods.
     * @return This shall return the report as a {@code String}.
     */
    public static String toStringDepth(final Hashtable<String, int[]> depthReport)
    {
        final StringBuilder sb = new StringBuilder();

        for (final String htmlTag : depthReport.keySet())
        {
            final int[] arr = depthReport.get(htmlTag);

            sb.append(
                "HTML Element: [" + htmlTag + "]:\t" +
                "Min-Depth: " + arr[0] + ",\tMax-Depth: " + arr[1] + ",\tCount: " + arr[2] + "\n"
            );
        }

        return sb.toString();
    }


    /**
     * Converts a balance report to a {@code String}, for printing.
     * 
     * @param balanceCheckReport This should be a {@code Hashtable} returned by any of the
     * balance-check methods.
     * 
     * @return This shall return the report as a {@code String}.
     */
    public static String toStringBalance(final Hashtable<String, Integer> balanceCheckReport)
    {
        final StringBuilder sb = new StringBuilder();

        int maxTagLen = 0, maxValStrLen = 0, maxAbsValStrLen = 0;

        // For good spacing purposes, we need the length of the longest of the tags.
        for (final String htmlTag : balanceCheckReport.keySet())
            if (htmlTag.length() > maxTagLen)
                maxTagLen = htmlTag.length();

        // 17 is the length of the string below, 2 is the amount of extra-space needed
        maxTagLen += 17 + 2; 

        for (final int v : balanceCheckReport.values())
        {
            final String vStr = "" + v;
            if (vStr.length() > maxValStrLen) maxValStrLen = vStr.length();

            final String absStr = "" + Math.abs(v);
            if (absStr.length() > maxAbsValStrLen) maxAbsValStrLen = absStr.length();
        }

        int val = 0;
        for (final String htmlTag : balanceCheckReport.keySet()) sb.append(
            StringParse.rightSpacePad("HTML Element: [" + htmlTag + "]:", maxTagLen) +

            StringParse.rightSpacePad(
                ("" + (val = balanceCheckReport.get(htmlTag).intValue())),
                maxValStrLen
            ) +

            NOTE(val, htmlTag, maxAbsValStrLen) +
            "\n"
        );

        return sb.toString();
    }

    private static String NOTE(
            final int       val,
            final String    htmlTag,
            final int       padding
        )
    {
        if (val == 0) return "";

        else if (val > 0) return
            ", which implies " + StringParse.rightSpacePad("" + Math.abs(val), padding) +
            " unclosed <" + htmlTag + "> element(s)";

        else return
            ", which implies " + StringParse.rightSpacePad("" + Math.abs(val), padding) +
            " extra </" + htmlTag + "> element(s)";
    }

    /**
     * Converts a balance report to a {@code String}, for printing.
     * 
     * @param balanceCheckReport This should be a {@code Hashtable} returned by any of the
     * balance-check methods.
     * 
     * @return This shall return the report as a {@code String}.
     * 
     * @throws IllegalArgumentException This exception throws if the length of the two input arrays
     * are not equal.  It is imperative that the balance report being printed was created by the
     * html-tags that are listed in the HTML Token var-args parameter.  If the two arrays are the
     * same length, but the tags used to create the report Hashtable are not the same ones being
     * passed to the var-args parameter {@code 'htmlTags'} - <I>the logic will not know the
     * difference, and no exception is thrown.</I>
     */
    public static String toStringBalance(
            final int[]     balanceCheckReport,
            final String... htmlTags
        )
    {
        if (balanceCheckReport.length != htmlTags.length) throw new IllegalArgumentException(
            "The balance report that you are checking was not generated using the html token " +
            "list provided, they are different lengths.  balanceCheckReport.length: " +
            "[" + balanceCheckReport.length + "]\t htmlTags.length: [" + htmlTags.length + "]"
        );

        final StringBuilder sb = new StringBuilder();

        for (int i=0; i < balanceCheckReport.length; i++)
            sb.append("HTML Element: [" + htmlTags[i] + "]:\t" + balanceCheckReport[i] + "\n");

        return sb.toString();
    }
}