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
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
package Torello.HTML;

import java.util.*;
import java.util.stream.*;

import Torello.HTML.NodeSearch.*;
import Torello.Java.*;

import Torello.Java.Additional.Ret2;
import Torello.HTML.Tools.Images.IF;

/**
 * Tools to retrieve and insert tags into the {@code <HEAD>} of a web-page.
 * 
 * <EMBED CLASS='external-html' DATA-FILE-ID=FEATURES>
 */
@Torello.JavaDoc.StaticFunctional
public class Features
{
    private Features() { }

    /** Error Message that is used repeatedly. */
    public static final String NO_HEADER_MESSAGE =
        "You are attempting to insert an HTML INSERT-STR, but such an element belongs in the " +
        "page's header.  Unfortunately, the page or sub-page you have passed does not have a " +
        "<HEAD>...</HEAD> sub-section.  Therefore, there is no place to insert the elements.";

    /**
     * This {@code String} may be inserted in the HTML <B STYLE='color: red;'>
     * {@code <HEAD> ... </HEAD>}</B> section to add a "logo-image" at the top-left corner of the
     * Web-Browser's tab for the page when it loads.  This logo is called a {@code 'favicon'}.
     * 
     * @see #insertFavicon(Vector, String)
     * @see #hasFavicon(Vector)
     */
    public static final String favicon =
        "<LINK REL='icon' TYPE='image/INSERT-IMAGE-TYPE-HERE' HREF='INSERT-URL-STRING-HERE' />";

    /**
     * This {@code String} may be inserted in the HTML <B STYLE='color: red;'>
     * {@code <HEAD> ... </HEAD>}</B> section to add a <B>Cascading Style Sheet</B> (a
     * {@code '.css'} file) to your page.
     * 
     * <BR /><BR />The web-browser that ultimately loads the HTML that you are exporting will
     * render the style elements across all the HTML elements in your page that match their
     * respective CSS-Selectors.  Without going into a big diatribe about how CSS works, just know
     * that the {@code String} used to build / instantiate a new {@link TagNode} with an externally
     * linked {@code CSS}-Page is provided here, by this field.
     * 
     * @see #insertCSSLink(Vector, String)
     * @see #getAllCSSLinks(Vector)
     */
    public static final String cssExternalSheet =
        "<LINK REL=stylesheet TYPE='text/css' HREF='INSERT-URL-STRING-HERE' />";

    /**
     * This {@code String} may be inserted in the HTML <B STYLE='color: red;'>
     * {@code <HEAD> ... </HEAD>}</B> section to add a <B>Cascading Style Sheet</B> (a
     * {@code '.css'} file) to your page.  This particular {@code String}-Constant Field includes /
     * allows for a {@code MEDIA}-Attribute / Inner-Tag.
     * 
     * @see #insertCSSLink(Vector, String)
     * @see #insertCSSLink(Vector, String, String)
     * @see #getAllCSSLinks(Vector)
     */
    public static final String cssExternalSheetWithMediaAttribute = 
        "<LINK REL=stylesheet TYPE='text/css' HREF='INSERT-URL-STRING-HERE' " +
            "MEDIA='INSERT-MEDIA-ATTRIBUTE-VALUE-HERE' />";

    /**
     * This {@code String} may be inserted in the HTML <B STYLE='color: red;'>
     * {@code <HEAD> ... </HEAD>}</B> section to add an externally-linked
     * <B>Java-Script File</B> ({@code '.js'} File) to your page.
     * 
     * <BR /><BR />The Web-Browser will download this <B>Java-Script</B> page from the
     * {@code URL} that you ultimately provide and (hopefully) load all your variable definitions
     * and methods when the page loads.
     *
     * <BR /><BR /><B CLASS=JDDescLabel>Closing {@code </SCRIPT>} Tag:</B>
     * 
     * <BR />Inserting an external <B>Java-Script</B> Page has one important difference vis-a-vis
     * inserting an external CSS-Page.  Inserting a link to a {@code '.js'} page requires
     * <B><I>both</I></B> the opening
     * <B STYLE='color: red;'>{@code <SCRIPT ..>}</B> <B><I>and</I></B> the closing
     * <B STYLE='color: red;'>{@code </SCRIPT>}</B>
     * Tags.
     * 
     * <BR /><BR />This is expected and required even-when / especially-when there is no actual
     * java-script code being placed on the {@code '.html'} page itself.  Effectively, regardless
     * of whether you are putting actual java-script code into / inside your HTML page, or you are 
     * just inserting a link to a {@code '.js'} File on your server - <I>you must always create
     * both the open and the closed HTML
     * <B STYLE='color: red;'>{@code <SCRIPT SRC='...'></SCRIPT>}</B> tags and insert them into
     * your Vectorized-HTML Web-Page</I>.
     * 
     * <BR /><BR />In the brief example below, it should be clear that even though the
     * {@code SCRIPT}-Tags do not enclose any <B>Java-Script</B>, both the open and the closed
     * versions of the tag are placed into the HTML-File.
     *
     * <DIV CLASS="HTML">{@code
     * <!-- This is a short note about including the HTML SCRIPT element in your web-pages. -->
     * <HTML>
     * <HEAD>
     * <!-- Version #1 Inserting a java-script 'variables & functions' external-page -->
     * <SCRIPT TYPE='text/javascript' SRC='/script/javaScriptFiles/functions.js'>
     * </SCRIPT>
     * <!-- Right here (line above) we always need the closing Script-tag, even when there is no
     *      actual java-script present, and the methods/variables are going to be downloaded from
     *      the java-script file identified in by the SRC="..." attribute! --> 
     *
     * <SCRIPT TYPE='text/javascript'>
     * var someVar1;
     * var someVar2;
     * 
     * function someFunction()
     * { return;    }
     * 
     * </SCRIPT> <!-- Either way, the closing-script tag is expected. -->
     * }</DIV>
     *
     * @see #insertExternalJavaScriptLink(Vector, String)
     * @see #getAllExternalJSLinks(Vector)
     */
    public static final String javaScriptExternalPage =
        "<SCRIPT TYPE='text/javascript' SRC='INSERT-URL-STRING-HERE'>";

    /**
     * If you have pages on your site that are almost identical, then you may need to inform search
     * engines which one to prioritize. Or you might have syndicated content on your site which was
     * republished elsewhere. You can do both of these things without incurring a duplicate content
     * penalty – as long as you use a {@code CANONICAL}-Tag.
     *
     * <BR /><BR />Instead of confusing Google and missing your ranking on the SERP's, you are
     * guiding the crawlers as to which URL counts as the “main” one. This places the emphasis on
     * the right URL and prevents the others from cannibalizing your SEO.
     *
     * <BR /><BR />Use {@code CANONICAL}-Tags to avoid having problems with duplicate content that
     * may affect your rankings.
     * 
     * <BR /><BR /><HR><BR />
     * 
     * The content of this Documentation Page was copied from a page on the web-domain
     * {@code 'http://searchenginewatch.com'}.  It was lifted on May 24th, 2019.
     * 
     * <BR /><BR />See link below, if still valid:
     * 
     * <BR /><A
     * HREF="https://searchenginewatch.com/2018/04/04/a-quick-and-easy-guide-to-meta-tags-in-seo/">
     * https://searchenginewatch.com/2018/04/04/a-quick-and-easy-guide-to-meta-tags-in-seo/ </A>
     * 
     * @see #insertCanonicalURL(Vector, String)
     * @see #hasCanonicalURL(Vector)
     */
    public static final String canonicalTag = 
        "<LINK REL=canonical HREF='INSERT-URL-STRING-HERE' />";

    /** This is a new-line {@code HTMLNode} */
    protected static final TextNode NEWLINE = new TextNode("\n");

    /**
     * This method checks whether the {@code String}-Parameter {@code 's'} contains a
     * Single-Quotations Punctuation-Mark anywhere inside that {@code String}. If so, a properly
     * formatted exception is thrown.  This is used as an internal Helper-Method.
     * 
     * @param s This may be any Java {@code String}, but generally it is one used to insert into an
     * HTML {@code CONTENT}-Attribute.
     * 
     * @throws QuotesException <EMBED CLASS='external-html' DATA-PARAM=s DATA-FILE-ID=FT_Q_EX>
     */
    protected static void checkForSingleQuote(String s)
    {
        int pos;

        if ((pos = s.indexOf("'")) != -1) throw new QuotesException(
            "The passed string-parameter may not contain a single-quote punctuation mark.  " +
            "Yours was: [" + s + "], and has a single-quotation mark at string-position " +
            "[" + pos + "]"
        );
    }

    /**
     * This inserts a favicon HTML link element into the right location so that a particular
     * Web-Page will render an "browser icon image" into the top-left corner of the Web-Page's
     * Browser-Tab.
     *
     * @param html <EMBED CLASS='external-html' DATA-FILE-ID=FT_HTML_PARAM>
     *
     * @param imageURLAsString <EMBED CLASS='external-html' DATA-FIELD=favicon
     *      DATA-FILE-ID=FT_STR_INS_PARAM>
     * 
     * @throws NodeNotFoundException <EMBED CLASS='external-html' DATA-FILE-ID=FT_NNF_EX>
     * 
     * @throws QuotesException <EMBED CLASS='external-html' DATA-PARAM=imageURLAsString
     *      DATA-FILE-ID=FT_Q_EX>
     *
     * @see #favicon
     * @see #checkForSingleQuote(String)
     */
    public static void insertFavicon(Vector<HTMLNode> html, String imageURLAsString)
    {
        // Insert the Favicon <LINK ...> element into the <HEAD> section of the input html page.
        // <link rel='icon' type='image/INSERT-IMAGE-TYPE-HERE' href='INSERT-URL-STRING-HERE' />

        checkForSingleQuote(imageURLAsString);

        // The HTML Page must have a <HEAD> ... </HEAD> section, or an exception shall throw.
        DotPair header = TagNodeFindInclusive.first(html, "head");

        if (header == null) throw new NodeNotFoundException
            (NO_HEADER_MESSAGE.replace("INSERT-STR", "favicon <LINK> element"));

        String ext = IF.getGuess(imageURLAsString).extension;

        if (ext == null) throw new IllegalArgumentException(
            "The Image-Type of the 'imageURLAsString' parameter could not be determined.  " +
            "The method IF.getGuess(faviconURL) returned null.  Please provide a favicon with " +
            "standard image file-type.  This is required because the image-type is required " +
            "to be placed inside the HTML <LINK TYPE=... HREF=...> Element 'TYPE' Attribute."
        );

        // Build a new Favicon TagNode.
        TagNode faviconTN = new TagNode
            ("<LINK REL='icon' TYPE='image/" + ext + "' HREF='" + imageURLAsString + "' />");

        // Insert the Favicon into the page.  Put it at the top of the header, just after <HEAD>
        Util.insertNodes(html, header.start + 1, NEWLINE, faviconTN, NEWLINE);
    }

    /**
     * This method will search for an HTML <B STYLE='color: red;'>{@code <LINK REL="icon" ...>}</B>
     * Tag, in hopes of finding a {@code REL}-Attribute  whose value is {@code 'icon'}.
     * 
     * <BR /><BR />When this method finds such a tag, it will return the
     * <B STYLE='color: red;'>value</B> of that Tag's {@code HREF}-Attribute.
     * 
     * @param html <EMBED CLASS='external-html' DATA-FILE-ID=HTMLVEC>
     * 
     * @return This method will return the {@code String}-<B STYLE='color: red;'>value</B> of the
     * {@code HREF}-Attribute found inside the {@code LINK}-Tag.
     * 
     * If this page or sub-page does not have such a tag with an {@code HREF}-Attribute, then null
     * is returned.
     *
     * <BR /><BR /><B STYLE="color: red;">NOTE:</B> In the event that multiple copies
     * of the HTML {@code LINK}-Tag are found, and more than one of these tags has a
     * {@code REL}-Attribute with a <B STYLE='color: red;'>value</B> equal to {@code "icon"}, then
     * this method will simple return the first of the {@code 'favicon'} tags that were found.
     * 
     * <BR /><BR />An (albeit erroneous) page, with multiple favicon definitions, will not cause
     * this method to throw an exception.
     * 
     * @see InnerTagGet
     * @see #favicon
     * @see TagNode#AV(String)
     */
    public static String hasFavicon(Vector<? extends HTMLNode> html)
    {
        // InnerTagGet.all: Returns a vector of TagNode's that resemble: <LINK rel="icon" ...>
        //
        // EQ_CI_TRM: Check the 'rel' Attribute-Value using a Case-Insensitive, Equality
        //            String-Comparison.
        //            Trim the 'rel' Attribute-Value String of possible leading & trailing
        //            White-Space before performing the comparison.

        Vector<TagNode> list = InnerTagGet.all
            (html, "LINK", "REL", TextComparitor.EQ_CI_TRM, "icon");

        // If there were no HTML "<LINK ...>" elements with REL='ICON' attributes, then
        // there was no favicon.

        if (list.size() == 0) return null;

        // Just in case there were multiple favicon <LINK ...> tags, just return the first
        // one found.  Inside of a <LINK REL="icon" HREF="..."> the 'HREF' Attribute contains
        // the Image-URL.  Use TagNode.AV("HREF") to retrieve that image url.

        String s;
        for (TagNode tn : list) if ((s = tn.AV("HREF")) != null) return s;

        // If for some reason, none of these <LINK REL='ICON' ...> elements had an "HREF" 
        // attribute, then just return null.

        return null;
    }

    /**
     * This inserts an HTML {@code LINK}-Tag into Web-Page parameter {@code 'html'} with the
     * purpose of linking an externally-defined <B>Cascading Style Sheet</B> (also known as a
     * {@code CSS}-Page) into that Page-{@code Vector}.
     *
     * @param html <EMBED CLASS='external-html' DATA-FILE-ID=FT_HTML_PARAM>
     *
     * @param externalCSSFileURLAsString <EMBED CLASS='external-html' DATA-FIELD=cssExternalSheet
     *      DATA-FILE-ID=FT_STR_INS_PARAM>
     * 
     * @throws NodeNotFoundException <EMBED CLASS='external-html' DATA-FILE-ID=FT_NNF_EX>
     * 
     * @throws QuotesException <EMBED CLASS='external-html' DATA-PARAM=externalCSSFileURLAsString
     *      DATA-FILE-ID=FT_Q_EX>
     *
     * @see #cssExternalSheet
     * @see #cssExternalSheetWithMediaAttribute
     * @see #insertCSSLink(Vector, String, String)
     * @see #getAllCSSLinks(Vector)
     * @see #checkForSingleQuote(String)
     * @see DotPair
     * @see TagNode
     */
    public static void insertCSSLink(Vector<HTMLNode> html, String externalCSSFileURLAsString)
    {
        // Inserts an external CSS Link into the <HEAD> section of this html page vector
        // <link REL=stylesheet type='text/css' href='INSERT-URL-STRING-HERE' />

        checkForSingleQuote(externalCSSFileURLAsString);

        // The HTML Page must have a <HEAD> ... </HEAD> section, or an exception shall throw.
        DotPair header = TagNodeFindInclusive.first(html, "head");

        if (header == null) throw new NodeNotFoundException(
            NO_HEADER_MESSAGE.replace
                ("INSERT-STR", "externally-linked CSS page <LINK> element")
        );

        TagNode cssTN = new TagNode
            ("<LINK REL=stylesheet TYPE='text/css' HREF='" + externalCSSFileURLAsString + "' />");

        // Insert the Style-Sheet link into the page.  Put it at the top of the header,
        // just after <HEAD>

        Util.insertNodes(html, header.start + 1, NEWLINE, cssTN, NEWLINE);
    }

    /**
     * This inserts a <B>Cascading Style Sheet</B> with the extra {@code MEDIA}-Attribute using
     * an HTML {@code LINK}-Tag into the Vectorized-HTML Web-Page parameter {@code 'html'}
     * 
     * @param html <EMBED CLASS='external-html' DATA-FILE-ID=FT_HTML_PARAM>
     * 
     * @param externalCSSFileURLAsString <EMBED CLASS='external-html' DATA-FIELD=cssExternalSheet
     *      DATA-FILE-ID=FT_STR_INS_PARAM>
     * 
     * @param mediaInnerTagValue Externally linked CSS-Pages, which are included using the HTML
     * {@code LINK}-Tag may explicitly request a {@code MEDIA}-Attribute be inserted into that
     * Tag.  That {@code MEDIA}-Attribute may take one of five values.  In such a tag, the extra
     * attribute specifies when the listed CSS-Rules are to be applied.
     *
     * <BR /><BR />Listed here are the most common values for the {@code MEDIA}-Attribute:
     * 
     * <BR /><TABLE CLASS=JDBriefTable>
     * <TR>
     *      <TH>Attribute Value</TH>
     *      <TH>Intended CSS Meaning</TH>
     * </TR>
     * <TR>
     *      <TD>screen</TD>
     *      <TD>indicates for use on a computer screen</TD>
     * </TR>
     * <TR>
     *      <TD>projection</TD>
     *      <TD>for projected presentations</TD>
     * </TR>
     * <TR>
     *      <TD>handheld</TD>
     *      <TD>for handheld devices (typically with small screens)</TD></TR>
     * <TR>
     *      <TD>print</TD>
     *      <TD>to style printed Web-Pages</TD>
     * </TR>
     * <TR>
     *      <TD>all</TD>
     *      <TD>(default value) This is what most people choose. You can leave off the
     *          {@code MEDIA}-Attribute completely if you want your styles to be applied for all
     *          media types.
     *          </TD>
     * </TR>
     * </TABLE>
     * 
     * @throws NodeNotFoundException <EMBED CLASS='external-html' DATA-FILE-ID=FT_NNF_EX>
     * 
     * @throws QuotesException <EMBED CLASS='external-html' DATA-PARAM1=externalCSSFileURLAsString
     *      DATA-PARAM2=mediaInnerTagValue DATA-FILE-ID=FT_Q_EX_DOUBL>
     *
     * @see #cssExternalSheet
     * @see #cssExternalSheetWithMediaAttribute
     * @see #insertCSSLink(Vector, String)
     * @see #getAllCSSLinks(Vector)
     * @see #checkForSingleQuote(String)
     * @see DotPair
     */
    public static void insertCSSLink
        (Vector<HTMLNode> html, String externalCSSFileURLAsString, String mediaInnerTagValue)
    {
        // Inserts an external CSS Link (with 'media' attribute) into the <HEAD> section of
        // this html page vector 
        // <link REL=stylesheet type='text/css' href='INSERT-URL-STRING-HERE'
        //      media='INSERT-MEDIA-ATTRIBUTE-VALUE-HERE' />

        checkForSingleQuote(externalCSSFileURLAsString);
        checkForSingleQuote(mediaInnerTagValue);

        // The HTML Page must have a <HEAD> ... </HEAD> section, or an exception shall throw.
        DotPair header = TagNodeFindInclusive.first(html, "HEAD");

        if (header == null) throw new NodeNotFoundException(
            NO_HEADER_MESSAGE.replace
                ("INSERT-STR", "externally-linked CSS Style-Sheet LINK-Tag")
        );

        // Build the TagNode
        TagNode cssTN   = new TagNode(
            "<LINK REL=stylesheet TYPE='text/css' HREF='" + externalCSSFileURLAsString + "' " +
            "MEDIA='" + mediaInnerTagValue + "' />"
        );

        // Insert the Style-Sheet link into the page.  Put it at the top of the header, just
        // after <HEAD>

        Util.insertNodes(html, header.start + 1, NEWLINE, cssTN, NEWLINE);
    }

    /**
     * This will retrieve all linked CSS-Pages from Vectorized-HTML Web-Page parameter
     * {@code 'html'}.
     * 
     * @param html <EMBED CLASS='external-html' DATA-FILE-ID=HTMLVEC>
     * @return This will return the links as a list of {@link TagNode}'s'
     * @see #insertCSSLink(Vector, String)
     * @see #insertCSSLink(Vector, String, String)
     * @see InnerTagGet
     */
    public static Vector<TagNode> getAllCSSLinks(Vector<? extends HTMLNode> html)
    { 
        // InnerTagGet.all: Returns a vector of TagNode's that resemble: 
        //                  <LINK rel="stylesheet" ...>
        //
        // EQ_CI_TRM: Check the 'rel' Attribute-Value using a Case-Insensitive, Equality
        //            String-Comparison
        //            Trim the 'rel' Attribute-Value String of possible leading & trailing
        //            White-Space before performing the comparison.

        return InnerTagGet.all(html, "LINK", "REL", TextComparitor.EQ_CI_TRM, "stylesheet");
    }

    /**
     * This inserts an HTML <B STYLE='color: red;'>{@code '<LINK ...>'}</B> element into the proper
     * location for linking an externally-defined <B>Java-Script</B> (a {@code '.js'} File) into
     * the Web-Page.
     *
     * @param html <EMBED CLASS='external-html' DATA-FILE-ID=FT_HTML_PARAM>
     *
     * @param externalJSFileURLAsString
     * <EMBED CLASS='external-html' DATA-FIELD=javaScriptExternalPage DATA-FILE-ID=FT_STR_INS_PARAM>
     * 
     * @throws NodeNotFoundException <EMBED CLASS='external-html' DATA-FILE-ID=FT_NNF_EX>
     * 
     * @throws QuotesException <EMBED CLASS='external-html' DATA-PARAM=externalJSFileURLAsString
     *      DATA-FILE-ID=FT_Q_EX>
     *
     * @see #javaScriptExternalPage
     * @see #getAllExternalJSLinks(Vector)
     * @see #checkForSingleQuote(String)
     * @see TagNode
     * @see TextNode
     * @see DotPair
     * @see HTMLTags#hasTag(String, TC)
     */
    public static void insertExternalJavaScriptLink
        (Vector<HTMLNode> html, String externalJSFileURLAsString)
    {
        // Builds an external Java-Script link, and inserts it into the header portion of
        // this html page.
        // <script type='text/javascript' src='INSERT-URL-STRING-HERE'>

        checkForSingleQuote(externalJSFileURLAsString);

        // The HTML Page must have a <HEAD> ... </HEAD> section, or an exception shall throw.
        DotPair header = TagNodeFindInclusive.first(html, "HEAD");

        if (header == null) throw new NodeNotFoundException(
            NO_HEADER_MESSAGE.replace(
                "INSERT-STR", "externally-linked Java-Script <SCRIPT> ... </SCRIPT> elements")
        );

        // Build an HTML <SCRIPT ...> node, and a </SCRIPT> node.
        HTMLNode n = new TagNode
            ("<SCRIPT TYPE='text/javascript' SRC='" + externalJSFileURLAsString + "'>");

        HTMLNode closeN = HTMLTags.hasTag("script", TC.ClosingTags);

        // Insert the Java-Script link into the page.  Put it at the top of the header, just
        // after <HEAD>

        Util.insertNodes(html, header.start + 1, NEWLINE, n, closeN, NEWLINE);
    }

    /**
     * Inserting <B>Java-Script</B> directly onto an HTML-Page and including an external link to a
     * {@code '.js'} File are extremely similar tasks.  Either way, in both cases the construct is
     * simply:
     * 
     * <BR /><BR /><B STYLE='color: red;'>{@code <SCRIPT TYPE='text/javascript'> ... </SCRIPT>}</B>
     * 
     * <BR /><BR />When the actual functions and methods are pasted into an HTML-Page directly,
     * they are pasted into the {@code String} above where the ellipses {@code '...'} are.  When a
     * link is made to an external page from a directory on the same Web-Server - both the open and
     * the close HTML {@code SCRIPT}-Tag's must be included.
     * 
     * <BR /><BR />If just a link is being added, then the text-content of the {@code SCRIPT}-Tag
     * should just be left blank or empty.  Instead, the {@code URL} to the Java-Script Page is
     * added as an HTML {@code SRC}-Attribute.
     *
     * <BR /><BR />This method will retrieve any and all {@code 'SCRIPT'} nodes that meet the
     * following criteria:
     * 
     * <BR /><BR /><OL CLASS=JDOL>
     * <LI> The <B>Script Body</B> must be empty, meaning there is no Java-Script between the
     *      opening and closing {@code SCRIPT}-Tags
     *      </LI>
     * 
     * <LI> The HTML {@code SRC}-Attribute must contain a non-null, non-zero-length
     *      <B STYLE='color: red;'>value</B>
     *      </LI>
     * 
     * </OL>
     *
     * @param html <EMBED CLASS='external-html' DATA-FILE-ID=HTMLVEC>
     *
     * @return This will return a list of relative {@code URL's} to externally linked
     * <B>Java-Script</B> Pages as {@code String's}
     * 
     * @see InnerTagGetInclusive
     * @see #javaScriptExternalPage
     * @see #insertExternalJavaScriptLink(Vector, String)
     * @see TagNode
     * @see TextNode
     * @see TagNode#AV(String)
     * @see HTMLNode#str
     */
    public static String[] getAllExternalJSLinks(Vector<? extends HTMLNode> html)
    {
        // InnerTagGetInclusive.all: Returns a vector of TagNode's that resemble:
        //                              <SCRIPT TYPE="javascript" ...>
        //
        // CN_CI: Check the 'rel' Attribute-Value using a Case-Insensitive, "Contains"
        //        String-Comparison
        //        'contains' rather than 'equals' testing is done because this value may be
        //        "javascript", but it may also be "text/javascript"
        //
        // Inclusive: This means that everything between the <SCRIPT type="javascript"> ... and
        //            the closing </SCRIPT> tag are returned in a vector of vectors.

        Vector<Vector<HTMLNode>> v = InnerTagGetInclusive.all
            (html, "SCRIPT", "TYPE", TextComparitor.CN_CI, "javascript");

        Stream.Builder<String> b = Stream.builder();

        TOP:
        for (Vector<HTMLNode> scriptSection : v)
        {
            String srcValue = null;

            for (HTMLNode n : scriptSection)
            {
                if (n.isTagNode())
                    if ((srcValue = ((TagNode) n).AV("SRC")) != null)
                        break;

                if (n.isTextNode())
                    if (n.str.trim().length() > 0)
                        break TOP;
            }

            b.add(srcValue);
        }

        return b.build().toArray(String[]::new);
    }

    /**
     * This section will insert a Canonical-{@code URL} into Vectorized-HTML parameter
     * {@code 'html'}.  The {@code URL} itself will be inserted into an HTML {@code LINK}-Tag as
     * below:
     * 
     * <BR /><BR /><B STYLE='color: red;'>{@code <LINK REL=canonical HREF='the_url'>}</B>
     * 
     * <BR /><BR />Since HTML mandates that such elements be located in the {@code 'HEAD'} portion
     * of an HTML-Page, if the Vectorized-HTML parameter {@code 'html'} does not have a
     * {@code 'HEAD'} area, then this method shall throw a {@link NodeNotFoundException}.
     * 
     * <BR /><BR />Note that this exception is an unchecked / runtime exception.
     *
     * @param html <EMBED CLASS='external-html' DATA-FILE-ID=FT_HTML_PARAM>
     *
     * @param canonicalURLAsStr
     * <EMBED CLASS='external-html' DATA-FIELD=canonicalTag DATA-FILE-ID=FT_STR_INS_PARAM>
     * 
     * @throws NodeNotFoundException <EMBED CLASS='external-html' DATA-FILE-ID=FT_NNF_EX>
     * 
     * @throws QuotesException <EMBED CLASS='external-html' DATA-PARAM=canonicalURLAsStr
     *      DATA-FILE-ID=FT_Q_EX>
     *
     * @see #canonicalTag
     * @see #hasCanonicalURL(Vector)
     * @see #checkForSingleQuote(String)
     * @see TagNode
     * @see DotPair
     */
    public static void insertCanonicalURL(Vector<HTMLNode> html, String canonicalURLAsStr)
    {
        // Inserts a link element into the header of this page
        // <link REL=canonical href='INSERT-URL-STRING-HERE' />

        checkForSingleQuote(canonicalURLAsStr);

        // The HTML Page must have a <HEAD> ... </HEAD> section, or an exception shall throw.
        DotPair header = TagNodeFindInclusive.first(html, "HEAD");

        if (header == null) throw new NodeNotFoundException
            (NO_HEADER_MESSAGE.replace("INSERT-STR", "Canonical-url LINK-Tag"));

        // Builds the canonical <LINK ...> element
        TagNode linkTN  = new TagNode
            ("<LINK REL=canonical HREF='" + canonicalURLAsStr + "' />");

        // Insert the canonical-url into the page.  Put it at the top of the header, just
        // after <HEAD>

        Util.insertNodes(html, header.start + 1, NEWLINE, linkTN, NEWLINE);
    }

    /**
     * This method will check whether a Vectorized-HTML Page has an HTML
     * <B STYLE='color: red;'>{@code <LINK REL=canonical ...>}</B> Tag.  This tag is used to
     * inform Search-Engines whether or not this page <I>surrenders</I> or <I>relays</I> to a
     * "Canonical-{@code URL}".
     * 
     * <BR /><BR />Canonical-Pages help Search-Engines index large web-sites by providing a root or
     * Master-{@code URL} to which all sub-pages may point.  Such {@code URL's} are often (but not
     * always) like a "Table of Contents".
     * 
     * <BR /><BR />The primary goal of having a canonical is to avoid forcing Search-Engines (and
     * their users) from sifting through and indexing every page of a large Web-Site, and instead
     * focusing on either an introductory T.O.C. or a Title-Page.
     *
     * @param html <EMBED CLASS='external-html' DATA-FILE-ID=HTMLVEC>
     * 
     * @return This will return whatever text was placed inside the canonical-url
     * {@code HREF='some_url'} attribute/value pair of the HTML link tag.  If there were no HTML
     * {@code <LINK REL=canonical HREF='some_url'>} tag, then this method will return null.
     * 
     * @throws MalformedHTMLException This exception will be thrown if there are multiple html tags
     * that match the link, and REL=canonical search criteria requirements.  If an HTML element
     * {@code <link REL=canonical>} is found, but that element does not have an
     * {@code href='...'} attribute, or that attribute is of zero length, then this a situation
     * that will also force this exception to throw.
     * 
     * @see InnerTagGet
     * @see #canonicalTag
     * @see #insertCanonicalURL(Vector, String)
     * @see TagNode#AV(String)
     */ 
    public static String hasCanonicalURL(Vector<? extends HTMLNode> html)
        throws MalformedHTMLException
    {
        // InnerTagGet.all: Returns a vector of TagNode's that resemble:
        //                  <LINK rel="canonical" ...>
        //
        // EQ_CI_TRM: Check the 'rel' Attribute-Value using a Case-Insensitive, Equality
        //            String-Comparison
        //            Trim the 'rel' Attribute-Value String of possible leading & trailing
        //            White-Space before performing the comparison.

        Vector<TagNode> v = InnerTagGet.all
            (html, "LINK", "REL", TextComparitor.EQ_CI_TRM, "canonical");

        if (v.size() == 0) return null;

        if (v.size() > 1) throw new MalformedHTMLException(
            "The Web-Page you have passed has precisely " + v.size() +
            " Canonical-URL LINK-Tags, but it may not have more than 1.  This is " +
            "invalid HTML."
        );

        String s = v.elementAt(0).AV("href");

        if (s == null) throw new MalformedHTMLException(
            "The HTML LINK-Tag that was retrieved, contained a " +
            "REL=canonical Attribute-Value pair, but did not have an HREF-Attribute." +
            "This is invalid HTML."
        );

        if (s.length() == 0) throw new MalformedHTMLException(
            "The HTML LINK-Tag that was retrieved contained a zero-length " +
            "String as the Attribute-Value for the HREF-Attribute.   This is not " +
            "invalid, but poorly formatted HTML."
        );

        return s;
    }

    /**
     * Tools made specifically for the {@code <META>} tags in the {@code <HEAD>} of a web-page.
     *
     * <EMBED CLASS='external-html' DATA-FILE-ID=FEATURES_META>
     */
    @Torello.JavaDoc.StaticFunctional
    public static class Meta
    {
        private Meta() { }


        // ****************************************************************************************
        // ****************************************************************************************
        // Static String-Constants (the tags!)
        // ****************************************************************************************
        // ****************************************************************************************


        /**
         * This is the most common HTML <B STYLE='color: red;'>{@code <META ... >}</B> Tag.
         * 
         * @see #getAllMetaTagNames(Vector)
         * @see #insertMetaTagName(Vector, MetaTagName, String)
         */
        public static final String metaTagName =
            "<META NAME='INSERT-NAME-STRING-HERE' CONTENT='INSERT-CONTENT-STRING-HERE'>";

        /**
         * This HTML <B STYLE='color: red;'>{@code <META ...>}</B> Tag is less frequently used, but
         * does provide some properties needed and used by various Web-Servers.  It is the
         * <B>{@code 'ITEMPROP'}</B> Meta-Tag.
         * 
         * @see #getItemProp(Vector, String)
         * @see #insertItemProp(Vector, String, String)
         */
        public static final String metaTagItemProp =
            "<META ITEMPROP='INSERT-ITEMPROP-STRING-HERE' CONTENT='INSERT-CONTENT-STRING-HERE'>";
        
        /**
         * <EMBED CLASS='external-html' DATA-PROP=robots DATA-FILE-ID=FEATURES_HTTP_EQUIV>
         * 
         * @see #getHTTPEquiv(Vector, String)
         * @see #insertHTTPEquiv(Vector, String, String)
         */
        public static final String metaTagHTTPEquiv =
            "<META HTTP-EQUIV='INSERT-HTTP-EQUIV-STRING-HERE' CONTENT='INSERT-CONTENT-STRING-HERE'>";

        /**
         * <EMBED CLASS='external-html' DATA-PROP=robots DATA-FILE-ID=FEATURES_META_PROP>
         * 
         * A {@code Robots}-Property Meta-Tag lets you utilize a granular, page-specific approach
         * to controlling how an individual page should be indexed and served to users in
         * Search-Engine results.
         * 
         * @see #insertRobots(Vector, boolean, boolean)
         * @see #getAllRobots(Vector)
         */
        public static final String robotsMetaTag =
            "<META NAME=robots CONTENT='INSERT-CONTENT-STRING-HERE'>";

        /**
         * <EMBED CLASS='external-html' DATA-PROP=description DATA-FILE-ID=FEATURES_META_PROP>
         * 
         * When search engines crawl Internet Web-Pages to read the provided key-words and
         * descriptions used for indexing, this particular Meta-Tag Property is one of the first
         * those crawlers will look at.
         * 
         * <BR /><BR />You may include a {@code Description}-Property in the {@code 'HEAD'} portion
         * of your site’s main-page.  A {@code META}-Description can influence both a Search-Engine's
         * Web-Crawlers, and ultimately the click-through rates of your readers.
         * 
         * <BR /><BR />Google has stated that Meta-Tag {@code Description}-Properties are NOT used
         * to rank pages.
         * 
         * @see #insertDescription(Vector, String)
         * @see #hasDescription(Vector)
         */
        public static final String descriptionMetaTag =
            "<META NAME=description CONTENT='INSERT-DESCRIPTION-OR-KEYWORDS-HERE'>";

        /**
         * <EMBED CLASS='external-html' DATA-FILE-ID=FEATURES_UTF8>
         * 
         * @see #insertUTF8MetaTag(Vector)
         * @see #hasUTF8MetaTag(Vector)
         */
        public static final String UTF8MetaTag =
            "<META HTTP-EQUIV='Content-Type' CONTENT='text/html; charset=utf-8'>";

        /**
         * <EMBED CLASS='external-html' DATA-FILE-ID=FEATURES_OPEN_GRAPH>
         * 
         * @see #insertOGMetaTag(Vector, String, String)
         * @see #getAllOGMetaTags(Vector)
         */
        public static final String openGraphMetaTag =
            "<META PROPERTY='og:INSERT-OG-PROPERTY-HERE' CONTENT='INSERT-OG-VALUE-HERE'>";

        /** All Open-Graph Property names. */
        public static final TreeMap<String, String> openGraphProperties = new TreeMap<>();

        /**
         * <EMBED CLASS='external-html' DATA-PROP=keywords DATA-FILE-ID=FEATURES_META_PROP>
         * 
         * A {@code KeyWords}-Property helps identify relevant, pertinent or 'germane' words that
         * describe the content of a Web-Site or Web-Page to a Web-Indexing or Web-Search
         * Organization.
         * 
         * @see #insertKeyWords(Vector, String[])
         * @see #getAllKeyWords(Vector)
         */
        public static final String keyWordsMetaTag =
            "<META NAME=keywords CONTENT='INSERT-COMMA-SEPARATED-KEYWORDS-HERE'>";

        /**
         * <EMBED CLASS='external-html' DATA-PROP=author DATA-FILE-ID=FEATURES_META_PROP>
         * 
         * This helps identify Web-Sites or Web-Pages "Author-Names" to Web-Indexing and Web-Search
         * Organizations.
         * 
         * @see #insertAuthor(Vector, String)
         * @see #hasAuthor(Vector)
         */
        public static final String authorMetaTag =
            "<META NAME=author CONTENT='INSERT-AUTHOR-NAME-HERE'>";


        // ****************************************************************************************
        // ****************************************************************************************
        // Retrieve all Meta-Tags as a java.util.Properties instance
        // ****************************************************************************************
        // ****************************************************************************************


        /**
         * This simple method will retrieve a {@code java.util.Properties} object for each and
         * every HTML <B STYLE='color: red'>{@code <META ...>}</B> tag found within a
         * Vectorized-HTML Web-Page.
         * 
         * @param page Any Vectorized-HTML page.  It is expected that this page contain a few
         * {@code META}-Tags.  If not, the method will still return an empty
         * {@code Vector<Properties>} having {@code size()} of zero.
         * 
         * @return The Java {@code 'Properties'} object that is returned from a call to
         * {@link TagNode#allAV()}
         * 
         * @see TagNode#allAV()
         * @see TagNodeGet
         */
        public static Vector<Properties> getAllMeta(Vector<HTMLNode> page)
        {
            Vector<Properties> ret = new Vector<>();

            // Retrieve all TagNode's that are HTML <META ...> Elements.  Invoke TagNode.allAV()
            // on each of these nodes to retrieve a java.util.Properties instance.\
            //
            // NOTE: These "Properties" could possibly be combined into a single Properties
            //       instance, but because of the ever-changing nature of Web-Page 
            //       Meta-Information tags, this is not employed here.  It is an exercise
            //       left to the programmer.

            for (TagNode tn : TagNodeGet.all(page, TC.OpeningTags, "META"))
                ret.add(tn.allAV());

            return ret;
        }


        // ****************************************************************************************
        // ****************************************************************************************
        // Retrieve NAME/Property Meta-Tags
        // ****************************************************************************************
        // ****************************************************************************************


        /**
         * This method will find an HTML
         * <B STYLE='color: red;'>{@code <META NAME=... CONTENT=...>}</B> element whose
         * {@code NAME}-Attribute has a {@code String}-value equal-to (<I>ignoring case</I>) the
         * value of the provided {@code String}-parameter {@code 'name'}.
         * 
         * <BR /><BR />After this HTML {@code META}-Tag has been identified, the
         * {@code String}-value of it's {@code CONTENT}-Attribute will be extracted and returned.
         * 
         * <BR /><BR /><B CLASS=JDDescLabel>Returning null, Gracefully:</B>
         * 
         * <BR />If the page provided does not have an HTML Meta-Tag with a {@code NAME}-Attribute
         * whose <B STYLE='color: red;'>value</B> is {@code 'name'} or if such an element is
         * identified, but that tag does not have a {@code CONTENT}-Attribute, then this method
         * will return null.
         * 
         * <BR /><BR /><B CLASS=JDDescLabel>Case Insensitive Comparison:</B>
         * 
         * <BR />Before the comparison is done with the {@code 'name'} parameter, that
         * {@code String} is trimmed with {@code String.trim()}, and the comparison performed
         * <I>is done while ignoring case</I>.
         * 
         * @param html <EMBED CLASS='external-html' DATA-FILE-ID=HTMLVEC>
         * 
         * @param name The name of the {@code <META NAME=...>} Tag.
         * 
         * @return The {@code String}-<B STYLE='color: red;'>value</B> of the
         * {@code CONTENT}-Attribute for a Meta-Tag whose {@code NAME}-Attribute is equal to the
         * specified name provided by parameter {@code 'name'}.  If such information is not found
         * on the page, then this method shall return null.
         * 
         * @see #getItemProp(Vector, String)
         * @see #getHTTPEquiv(Vector, String)
         */
        public static String getMetaTagName(Vector<HTMLNode> html, String name)
        {
            // Find the first <META NAME=... CONTENT=...> tag element where the name equals
            // the string-value provided by parameter name.

            TagNode tn = InnerTagGet.first
                (html, "META", "NAME", TextComparitor.EQ_CI, name.trim());

            // If there are no <META NAME='NAME' CONTENT=...> elements found on the page,
            // then this method returns null.

            if (tn == null) return null;

            // Return the string-value of the attribute 'content'.  Note that if this
            // attribute isn't available, this method shall return 'null', gracefully.

            return tn.AV("CONTENT");
        }


        /**
         * This will retrieve all Meta-Tag's having {@code NAME}-Attribute and 
         * {@code CONTENT}-Attribute pairs.
         * 
         * @param html <EMBED CLASS='external-html' DATA-FILE-ID=HTMLVEC>
         * 
         * @return a {@code java.util.Hashtable} of all the Meta-Tag Name/Content pairs that do not
         * have null values.
         * 
         * @throws IllegalArgumentException  The method {@code MetaTagName.valueOf(...)} will throw
         * an Illegal Argument Exception if any of the {@code <META NAME=...>} elements use a value
         * of "NAME" that is not listed or identified in the Enumerated Type "MetaTagName".
         * 
         * <BR /><BR /><B><SPAN STYLE="color: red">ALTERNATIVE:</SPAN></B> As Internet Companies
         * come and go, pinning down a complete list of valid Meta Tag's that use the "NAME"
         * Attribute is a possibly misguided approach.  In lieu of eliminating the Enumerated-Type
         * {@code MetaTagName}, it should be easier to just use the standard TagNode search below:
         * 
         * <DIV CLASS="EXAMPLE">{@code
         * // This code should be used as an alternative to this method if there are non-standard
         * // HTML Meta Tag Names.  It uses the more fundamental InnerTagGet Method.
         *
         * // This will retrieve all <META ...> HTML Elements that have a "NAME" Property.
         * Vector<TagNode> metaTags = InnerTagGet.all(page, "meta", "name");
         * 
         * // This will print out those results:
         * for (TagNode metaTag : metaTags) System.out.println
         *     ("Name:\t" + metaTag.AV("name") + "\tContent:\t" + metaTag.AV("content"));
         * }</DIV>
         * 
         * @see MetaTagName
         * @see #metaTagName
         * @see #insertMetaTagName(Vector, MetaTagName, String)
         * @see InnerTagGet
         */
        public static Hashtable<MetaTagName, String> getAllMetaTagNames
            (Vector<? extends HTMLNode> html)
        {
            Hashtable<MetaTagName, String> ret = new Hashtable<>();

            // Converting the output "Vector<TagNode>" to a "Stream<TagNode>" by calling the
            // .stream() method mainly because java streams provide the very simple
            // 'filter(Predicate)' and 'forEach(Consumer)' methods.  Vector.removeIf and
            // Vector.forEach could also have been easily used as well.
    
            // InnerTagGet.all returns a vector containing all <META NAME=...> TagNode's where
            // the value of the 'name' attribute is one of the pre-defined MetaTagName
            // EnumeratedTypes.

            // NOTE: This is done via a java.util.function.Predicate<String> and a lambda
            //       expression

            InnerTagGet
                .all (html, "META", "NAME", (String nameAttributeValue) ->
                    MetaTagName.valueOf
                        (nameAttributeValue.toLowerCase().trim()) != null)

                .stream()
                .filter((TagNode tn) -> tn.AV("CONTENT") != null)

                .forEach((TagNode tn) ->

                    ret.put(
                        MetaTagName.valueOf(tn.AV("NAME").toLowerCase().trim()),
                        tn.AV("CONTENT")
                    ));

            return ret;
        }


        // ****************************************************************************************
        // ****************************************************************************************
        // Retrieve **SPECIFIC** NAME/Property Meta-Tags
        // ****************************************************************************************
        // ****************************************************************************************


        /**
         * This method looks for robots HTML <B STYLE='color: red;'>{@code <META NAME=robots>}
         * </B> tag, and returns the value of the {@code content}-Attribute.
         *
         * @param html <EMBED CLASS='external-html' DATA-FILE-ID=HTMLVEC>
         * 
         * @return This will return a vector of the robots named or specified by the HTML
         * Meta-Tag's present on this page.
         * 
         * <BR /><BR /><B><SPAN STYLE="color: red;">NOTE:</B></SPAN> Please do not be disturbed by
         * java-streams, they are of limited use, but once a programmer is accustomed to the words
         * above, they actually improve code-readability (<B><I>once in a while!</I></B>).  A
         * series of simple {@code for-loops} which eliminate-duplicates / add / sort would
         * accomplish the same task as above.
         *
         * @throws MalformedHTMLException If any invalid robot-strings are found on the page, this
         * method will throw an exception.  The impetus behind this is to prevent accidentally
         * ignoring newly found tags, or incorrect tags. The extraction of the robots Meta-Tag from
         * an HTML page can be performed manually, if throwing an exception is causing problems.
         * The code to do this is listed in the documentation of this method.
         * 
         * @see #robotsMetaTag
         * @see #insertRobots(Vector, boolean, boolean)
         */
        public static Vector<Robots> getAllRobots(Vector<? extends HTMLNode> html)
            throws MalformedHTMLException
        {
            // Here, again, using Java Streams can be sometimes useful - primarily whenever a
            // 'filter' operation is going to be used on a Vector.  Vector.removeIf works, BUT
            // this also extracts attribute values, and the original TagNode are discarded, and
            // replaced by the the <META> attributes.
            //
            // ALSO SALIENT: the "Arrays.asList" produces an array of string, and the "::addAll"
            //               puts each separate String in each array into the TreeSet.
            //
            // NOTE: The TreeSet also functions as a "duplicate checker" although this is also
            //       provided by Stream.distinct()
            //
            // InnerTagGet.all; Returns a vector of TagNode's that resemble:
            //      <META NAME="robots" ...>
            //
            // EQ_CI_TRM: Check the 'name' Attribute-Value using a Case-Insensitive, Equality 
            //            String-Comparison
            //            Trim the 'name' Attribute-Value String of possible leading & trailing
            //            White-Space before performing the comparison.

            TreeSet<String> temp = InnerTagGet
                .all        (html, "META", "NAME", TextComparitor.EQ_CI_TRM, "robots")
                .stream     ()
                .map        ((TagNode tn) -> tn.AV("CONTENT"))

                .filter     ((String contents) ->
                                (contents != null) && (contents.trim().length() > 0))

                .map        ((String contents) ->
                                Arrays.asList(StrCSV.CSV(contents.toLowerCase())))

                .collect    (TreeSet<String>::new, TreeSet::addAll, TreeSet::addAll);

            // I cannot use EXCEPTIONS and STREAMS together, there is no simple way.
            // It would be too ugly to read.

            Vector<Robots> ret = new Vector<>();

            // If an invalid robot-attribute is found, this will
            // throw a MalformedHTMLException

            for (String s : temp) ret.add(Robots.getRobot(s));

            return ret;
        }

        /**
         * This will retrieve the {@code 'robots'} Meta-Tag
         * Attribute-<B STYLE='color: red;'>value</B> present on a Web-Page.
         * 
         * <BR /><BR />If any of them are not in accordance with the tags listed in the
         * Enumerated-Type {@link Robots}, this will not cause a {@link MalformedHTMLException} to
         * throw.  Instead, the result will just be eliminated and ignored.  Take care that all of
         * the necessary {@code ROBOTS}-Tags are listed in the Enumerated-Type, and that there
         * are no "undefined, but necessary" robot elements to be found before using this method!
         *
         * @param html <EMBED CLASS='external-html' DATA-FILE-ID=HTMLVEC>
         * @return A vector of all the valid robots attribute values found on the web-page.
         * @see #robotsMetaTag
         * @see #insertRobots(Vector, boolean, boolean)
         * @see TagNode#AV(String)
         */
        public static Vector<Robots> getAllRobotsNOMHE(Vector<? extends HTMLNode> html)
        {
            // Java Streams, used here, filter out irrelevant meta tags, and also convert the
            // HTML Meta TagNode's into their their "CONTENT" Attribute String value.  The TreeSet
            // provides a duplicate check elimination and sorts the {@code String's} as well.
            //
            // ALSO SALIENT: the "Arrays.asList" produces an array of string, and the "::addAll"
            //               puts each separate String in each array into the TreeSet
            //
            // NOTE: The 'getRobotNOMHE' suppresses a possible exception, and converts such a
            //       situation to 'null.'  The suppressed-exception is the "MalformedHTMLException"
            //
            // InnerTagGet.all; Returns a vector of TagNode's that resemble:
            // <META NAME="robots" ...>
            //
            // EQ_CI_TRM: Check the 'name' Attribute-Value using a Case-Insensitive, Equality
            //            String-Comparison
            //            Trim the 'name' Attribute-Value String of possible leading & trailing
            //            White-Space before performing the comparison.

            return InnerTagGet
                .all        (html, "META", "NAME", TextComparitor.EQ_CI_TRM, "robots")
                .stream     ()
                .map        ((TagNode tn) -> tn.AV("CONTENT"))

                .filter     ((String contents) ->
                                (contents != null) && (contents.trim().length() > 0))

                .map        ((String contents) ->
                                Arrays.asList(StrCSV.CSV(contents.toLowerCase())))

                .collect    (TreeSet<String>::new, TreeSet::addAll, TreeSet::addAll)
                .stream     ()
                .map        ((String robotParam)    -> Robots.getRobotNOMHE(robotParam))
                .filter     ((Robots robot)         -> robot != null)
                .collect    (Collectors.toCollection(Vector<Robots>::new));
        }

        /**
         * This method will extract any / all HTML
         * <B STYLE='color: red;'>{@code <META NAME='keywords' ...>}</B> Meta-Tags, and then extract
         * the relevant page key-words.  These key-words will be returned as a Java
         * {@code String-Vector}.
         * 
         * @param html <EMBED CLASS='external-html' DATA-FILE-ID=HTMLVEC>
         * 
         * @return The list of words that were stored in the 'keywords' HTML Meta-Tags.  If there
         * were no keywords in any {@code 'KEYWORDS'} Meta-Tags, then an empty Java
         * {@code String[]}-Array is returned.
         *
         * <BR /><BR /><B CLASS=JDDescLabel>Java Stream's Utility:</B>
         * 
         * <BR />If the code below looks complicated, Java's Streams-Package does have a tendency
         * to make <I>simple things look difficult</I>.  However, once the {@code Stream}-Methods
         * are understood, it's usually pretty useful for actually being very concise.
         * 
         * <BR /><BR /><OL CLASS=JDOL>
         * <LI> Get all HTML {@code <META name="keywords" content="...">} elements</LI>
         * 
         * <LI> Extracts the {@code CONTENT}-Attribute, <I>and particularly the
         *      <B STYLE='color: red;'>value</B> stored there</I>
         *      </LI>
         * 
         * <LI> Removes blanks, and {@code nulls}</LI>
         * <LI> Converts a {@code String[]} to {@code List<String>}</LI>
         * <LI> Collects all the List<String> into a single java String-Array</LI>
         * </OL>
         * 
         * @see #insertKeyWords(Vector, String[])
         * @see #keyWordsMetaTag
         * @see TagNode
         * @see TagNode#AV(String)
         * @see StrCSV#CSV(String)
         */
        public static String[] getAllKeyWords(Vector<? extends HTMLNode> html)
        {
            // Java Streams here both filter irrelevant meta tags, and also convert the type from
            // TagNode to String... using the 'map' function.  Ultimately, those strings are
            // 'collected' into the returned vector.
            // ALSO SALIENT: the "Arrays.asList" produces an array of string, and the "::addAll"
            // puts each separate String into the returned Vector.

            // InnerTagGet.all: Returns a vector of TagNode's that resemble:
            // <META name="keywords" ...>
            //
            // EQ_CI_TRM: Check the 'name' Attribute-Value using a Case-Insensitive, Equality
            //            String-Comparison
            //            Trim the 'name' Attribute-Value String of possible leading & trailing
            //            White-Space before performing the comparison.

            return InnerTagGet.all(html, "META", "NAME", TextComparitor.EQ_CI_TRM, "keywords")
                .stream     ()
                .map        ((TagNode tn) -> tn.AV("content"))

                .filter     ((String contents) ->
                                (contents != null) && (contents.trim().length() > 0))

                .map        ((String contents) -> Arrays.asList(StrCSV.CSV(contents)))
                .collect    (Vector::new, Vector::addAll, Vector::addAll)
                .stream     ()
                .toArray    (String[]::new);
        }

        /**
         * This method attempts to retrieve a {@code 'description'}-Property Meta-Tag out of an
         * HTML_Page.  If no such Meta-Tag is found, then null is returned.
         * 
         * <BR /><BR />If a partial Meta-Tag is found, but that tag is incomplete, then a
         * {@link MalformedHTMLException} will be thrown.
         *
         * @param html <EMBED CLASS='external-html' DATA-FILE-ID=HTMLVEC>
         * 
         * @return The content-description that has been extracted from the HTML Meta-Tag
         * <B STYLE='color: red;'>{@code <META NAME="description" CONTENT="the-description">}</B>.
         * 
         * <BR /><BR />If this tag is not found, then null is returned.  If this tag is found, but
         * does not posses a {@code CONTENT}-Attribute, then a {@code MalformedHTMLException} is
         * thrown.
         *
         * @throws MalformedHTMLException This is thrown if there are multiple definitions of the
         * {@code 'ROBOTS'} Meta-Tag.  There ought to only be a single definition, and if multiple
         * are found, it would be better to identify why, and do the data-extraction manually.
         * 
         * This is en-lieu of randomly picking one of them, and randomly returning one of the
         * Meta-Tag's {@code CONTENT}-Attribute <B STYLE='color: red;'>value</B>.
         *
         * <BR />This exception will also be thrown if proper-values for {@code 'index'} or
         * {@code 'follow'} are not found in the {@code CONTENT}-Attribute of the
         * {@code 'ROBOTS'} Meta-Tag.
         *
         * <BR /><BR />These are probably unlikely occurrences.  This exception is a
         * Checked-Exception and must have a {@code try-catch} block or be declared thrown in your
         * method-declaration.
         *
         * @see #descriptionMetaTag
         * @see #insertDescription(Vector, String)
         * @see InnerTagGet
         */
        public static String hasDescription(Vector<? extends HTMLNode> html)
            throws MalformedHTMLException
        { 
            // InnerTagGet.all; Returns a vector of TagNode's that resemble:
            // <META NAME="description" ...>
            //
            // EQ_CI_TRM: Check the 'name' Attribute-Value using a Case-Insensitive, Equality
            //            String-Comparison
            //            Trim the 'name' Attribute-Value String of possible leading & trailing
            //            White-Space before performing the comparison.

            Vector<TagNode> v = InnerTagGet.all
                (html, "META", "NAME", TextComparitor.EQ_CI_TRM, "description");

            if (v.size() == 0) return null;

            if (v.size() > 1) throw new MalformedHTMLException(
                "You have asked for the value of the HTML 'description' <META ...> Tag, but " +
                "unfortunately there were multiple instances of this Tag on your page.  " +
                "This is poorly formatted HTML, and not allowed here."
            );

            String s = v.elementAt(0).AV("CONTENT");

            if (s == null) throw new MalformedHTMLException(   
                "An HTML Meta-Tag was found with a NAME-Attribute whose value was " +
                "'description,' but unfortunately this Meta-Tag did not posses a CONTENT-Attribute"
            );
        
            return s;
        }

        /**
         * This helps identify Web-Sites &amp; Web-Pages "author-names" to Web-Indexing and
         * Web-Search Organizations.
         * 
         * @param html <EMBED CLASS='external-html' DATA-FILE-ID=HTMLVEC>
         * 
         * @return This returns the author's name of a Web-Page, as delineated in the
         * {@code 'AUTHOR'} Meta-Tag, or null if the Web-Page parameter {@code 'html'} does not
         * have an {@code 'AUTHOR'} Meta-Tag.
         * 
         * @throws MalformedHTMLException If multiple {@code 'AUTHOR'} Meta-Tags are found, this
         * method is forced to throw an exception.  It is necessary to avoid "picking a favorite
         * author among a list".
         * 
         * <BR /><BR />HTML does not actually adhere to these exact requirements, so if there is
         * such a scenario with a page having multiple-authors, this method throws an exception in
         * order to avoid returning a {@code String[]}-Array or {@code Vector<String>} which would
         * be an alternative that would add unnecessary complexity.
         * 
         * <BR /><BR />If this method throws this exception, it is better to know about it, and
         * just perform the search again, using a manual {@code 'AUTHOR'} retrieval.  The code for
         * extracting these properties is, indeed listed directly at the bottom.
         * 
         * @see #insertAuthor(Vector, String)
         * @see #authorMetaTag
         * @see TagNode#AV(String)
         */
        public static String hasAuthor(Vector<? extends HTMLNode> html)
            throws MalformedHTMLException
        {
            // InnerTagGet.all: Returns a vector of TagNode's that resemble:
            // <META name="author" ...>
            //
            // EQ_CI_TRM: Check the 'name' Attribute-Value using a Case-Insensitive, Equality
            //            String-Comparison
            //            Trim the 'name' Attribute-Value String of possible leading & trailing
            //            White-Space before performing the comparison.

            Vector<TagNode> v = InnerTagGet.all
                (html, "META", "NAME", TextComparitor.EQ_CI_TRM, "author");

            if (v.size() > 1) throw new MalformedHTMLException(
                "This method has identified multiple author Meta-Tags.  To handle this " +
                "situation, the search should be performed manually using InnerTagGet, with " +
                "your code deciding what to do about the HTML Web-Page having multiple 'author' " +
                "Meta-Tags."
            );

            // No HTML TagNode's were found that resembled <META NAME=author ...>
            if (v.size() == 0) return null;

            // Just return the first one that was found, always check for 'null' first to
            // avoid the embarrassing NullPointerException.

            String author = v.elementAt(0).AV("CONTENT");

            if (author == null) return null;

            return author.trim();
        }


        // ****************************************************************************************
        // ****************************************************************************************
        // Retrieve HTTP-EQUIV Meta-Tags
        // ****************************************************************************************
        // ****************************************************************************************


        /**
         * This method will find an HTML
         * <B STYLE='color: red;'>{@code <META HTTP-EQUIV=... CONTENT=...>}</B> element whose
         * {@code HTTP-EQUIV}-Attribute's <B STYLE='color: red;'>value</B> is equal to the
         * {@code String}-Parameter {@code 'httpEquiv'} (ignoring case).
         * 
         * <BR /><BR />After such an HTML {@code META}-Tag has been identified, its 
         * {@code CONTENT}-Attribute {@code String}-value will be subsequently queried, extracted
         * and returned by this method.
         * 
         * <BR /><BR /><B CLASS=JDDescLabel>Returning null, Gracefully:</B>
         * 
         * <BR />If the page provided does not have an HTML Meta-Tag with a {@code NAME}-Attribute
         * whose <B STYLE='color: red;'>value</B> is {@code 'name'} or if such an element is
         * identified, but that tag does not have a {@code CONTENT}-Attribute, then this method
         * will return null.
         * 
         * <BR /><BR /><B CLASS=JDDescLabel>Case Insensitive Comparison:</B>
         * 
         * <BR />Before the comparison is done with the {@code 'httpEquiv'} parameter, that
         * {@code String} is trimmed with {@code String.trim()}, and the comparison performed
         * <I>is done while ignoring case</I>.
         * 
         * @param html <EMBED CLASS='external-html' DATA-FILE-ID=HTMLVEC>
         * 
         * @param httpEquiv The Attribute-<B STYLE='color: red;'>name</B> of the
         * {@code HTTP-EQUIV}-Attribute.
         * 
         * @return The {@code String}-value of the {@code CONTENT}-Attribute for a 
         * {@code META}-Tag whose {@code HTTP-EQUIV}-Attribute is equal to the specified name
         * provided by parameter {@code 'httpEquiv'}.
         * 
         * <BR /><BR />If no such tag is found on the page, then this method shall return null.
         */
        public static String getHTTPEquiv(Vector<HTMLNode> html, String httpEquiv)
        {
            // Find the first <META HTTP-EQUIV=... CONTENT=...> tag element where the name equals
            // the string-value provided by parameter 'httpEquiv'.

            TagNode tn = InnerTagGet.first
                (html, "META", "HTTP-EQUIV", TextComparitor.EQ_CI, httpEquiv.trim());

            // If there are no <META HTTP-EQUIV='httpEquiv' CONTENT=...> elements found on the
            // page, then this method returns null.

            if (tn == null) return null;

            // Return the string-value of the attribute 'content'.  Note that if this
            // attribute isn't available, this method shall return 'null', gracefully.

            return tn.AV("CONTENT");
        }

        /**
         * This method will find all HTML {@code HTTP-EQUIV}-Directives, and return them in a Java
         * {@code Properties} object.
         * 
         * @param html <EMBED CLASS='external-html' DATA-FILE-ID=HTMLVEC>
         * 
         * @return An instance of {@code java.util.Properties} containing all
         * {@code HTTP-EQUIV}-Directives.  If HTML-Page paramter {@code 'html'} does not have any
         * such Meta-Tags, then an empty {@code Properties} instance is returned, rather than null.
         * 
         * @throws MalformedHTMLException If the page provided has multiple definitions for the 
         * exact same {@code HTTP}-Header property, then this exception will throw.
         */
        public static Properties getAllHTTPEquiv(Vector<HTMLNode> html)
            throws MalformedHTMLException
        {
            Properties  ret     = new Properties();
            String      prev    = null;

            // Find the first <META HTTP-EQUIV=... CONTENT=...> tag element where the name equals
            // the string-value provided by parameter 'httpEquiv'.

            for (TagNode httpEquivTN : InnerTagGet.all(html, "META", "HTTP-EQUIV"))

                if ((prev = (String) ret.put
                    (httpEquivTN.AV("HTTP-EQUIV"), httpEquivTN.AV("CONTENT"))) != null)

                    throw new MalformedHTMLException(
                        "This HTML Page has multiple Meta-Tag Definitions for the HTTP-" +
                        "EQUIVALENT Property [" + httpEquivTN.AV("HTTP-EQUIV") + "].\n" +
                        "    " + prev + "\n" +
                        "and " + httpEquivTN.AV("CONTENT") + '\n'
                    );

            return ret;
        }

        /**
         * This will detect whether a {@code UTF-8} HTML Meta-Tag is included on this page.  Below
         * are examples of what such tags look like.
         * 
         * <DIV CLASS="HTML">{@code
         * <meta http-equiv="content-type" content="text/html; charset=UTF-8">
         * <meta charset="UTF-8">
         * }</DIV>
         *
         * @param html <EMBED CLASS='external-html' DATA-FILE-ID=HTMLVEC>
         *
         * @return {@code TRUE} If an appropriate HTML Meta-Tag identifying this page as a
         * {@code UTF-8} Character-Set Web-Site. will {@code FALSE} otherwise.
         * 
         * @see #hasUTF8MetaTag(Vector)
         * @see #UTF8MetaTag
         * @see StrCmpr#containsAND_CI(String, String[])
         * @see TagNode#AV(String)
         */
        public static boolean hasUTF8MetaTag(Vector<? extends HTMLNode> html)
        {
            String s;

            // InnerTagGet.all: Returns a vector of TagNode's that resemble:
            // <META http-equiv="content-type" ...>
            //
            // EQ_CI_TRM: Check the 'http-equiv' Attribute-Value using a Case-Insensitive, 
            //            Equality String-Comparison
            //            Trim the 'http-equiv' Attribute-Value String of possible leading & 
            //            trailing White-Space before performing the comparison.

            Vector<TagNode> v = InnerTagGet.all
                (html, "META", "HTTP-EQUIV", TextComparitor.EQ_CI_TRM, "content-type");

            for (TagNode tn : v)
                if ((s = tn.AV("CONTENT")) != null)
                    if (StrCmpr.containsAND_CI(s, "charset", "utf-8"))
                        return true;

            // InnerTagGet.aall retrieves all TagNode's that resemble <META charset="utf-8" ...>
            // EQ_CI_TRM: Equality-Test, Case-Insensitive, Trim any White-Space before 
            // performing comparison.

            v = InnerTagGet.all(html, "META", "CHARSET", TextComparitor.EQ_CI_TRM, "utf-8");

            for (TagNode tn : v)
                if ((s = tn.AV("CHARSET")) != null)
                    if (StrCmpr.containsAND_CI(s, "utf-8"))
                        return true;

            return false;
        }


        // ****************************************************************************************
        // ****************************************************************************************
        // Insert NAME/Property Meta-Tags
        // ****************************************************************************************
        // ****************************************************************************************


        /**
         * This does a very simple insertion of an HTML Meta-Tag for a specific type,
         * Meta-Tags that have both a {@code NAME}-Attribute and a {@code CONTENT}-Attribute
         * set.
         * 
         * @param html <EMBED CLASS='external-html' DATA-FILE-ID=FT_HTML_PARAM>
         * 
         * @param m This is any of the enumerated-types of specific Meta-Tag {@code NAME}-Attribute
         * &amp; {@code CONTENT}-Attribute pair / combinations.
         * 
         * @param contentAttributeValue This is the value that will be used to set the 
         * <B STYLE='color: red;'>value</B> for the {@code CONTENT}-Attribute.
         * 
         * @throws NodeNotFoundException <EMBED CLASS='external-html' DATA-FILE-ID=FT_NNF_EX>
         * 
         * @throws QuotesException <EMBED CLASS='external-html' DATA-PARAM=contentAttributeValue
         *      DATA-FILE-ID=FT_Q_EX>
         *
         * @see #metaTagName
         * @see #getAllMetaTagNames(Vector)
         * @see DotPair
         * @see TagNode
         */
        public static void insertMetaTagName
            (Vector<HTMLNode> html, MetaTagName m, String contentAttributeValue)
        { 
            // Builds and inserts a TagNode HTML Element that looks like:
            // <meta name='INSERT-NAME-STRING-HERE' content='INSERT-CONTENT-STRING-HERE'>

            // Single Quotes are used, so the attribute-value may not contain single quotes.
            checkForSingleQuote(contentAttributeValue);
    
            // The HTML Page must have a <HEAD> ... </HEAD> section, or an exception shall throw.
            DotPair header = TagNodeFindInclusive.first(html, "HEAD");

            if (header == null) throw new NodeNotFoundException
                (NO_HEADER_MESSAGE.replace("INSERT-STR", "<META NAME=... CONTENT=...> tag"));

            // Build a <META> tag, as in the comment above
            TagNode metaTN = new TagNode
                ("<META NAME='" + m.name + "' CONTENT='" + contentAttributeValue + "'>");

            // Insert the meta-tag into the page.  Put it at the top of the header,
            // just after <HEAD>

            Util.insertNodes(html, header.start + 1, NEWLINE, metaTN, NEWLINE);
        }

        /**
         * This does an insertion of a list of HTML Meta-Tags from a java Hashtable of Meta-Tag
         * Name-Attribute / Content-Attribute pairs.  All name-based Meta-Tags have both a
         * {@code NAME}-Attribute, and also a {@code CONTENT}-Attribute.
         * 
         * @param html <EMBED CLASS='external-html' DATA-FILE-ID=FT_HTML_PARAM>
         * 
         * @param metaTags This is a hash-table of the enumerated-types of specific Meta-Tag Name
         * property/content pairs.
         * 
         * @throws NodeNotFoundException <EMBED CLASS='external-html' DATA-FILE-ID=FT_NNF_EX>
         * 
         * @throws QuotesException If any of the <B STYLE='color: red;'>values</B> from the
         * <B STYLE='color:red'>key-value</B> pair hash-table contain a {@code String} that has a
         * single-quotation mark, anywhere inside the it.
         * 
         * @see #metaTagName
         * @see #getAllMetaTagNames(Vector)
         * @see #insertMetaTagName(Vector, MetaTagName, String)
         * @see TagNode
         */
        public static void insertMetaTagNames
            (Vector<HTMLNode> html, Hashtable<MetaTagName, String> metaTags)
        {
            // Builds and inserts a TagNode HTML Element that looks like:
            // "<meta name='INSERT-NAME-STRING-HERE' content='INSERT-CONTENT-STRING-HERE'";

            // The HTML Page must have a <HEAD> ... </HEAD> section, or an exception shall throw.
            DotPair header = TagNodeFindInclusive.first(html, "HEAD");

            if (header == null) throw new NodeNotFoundException
                (NO_HEADER_MESSAGE.replace("INSERT-STR", "<META NAME=... CONTENT=...> tag"));

            // Java Stream's can be addictive...  It is an easier way to build a list.
            Stream.Builder<HTMLNode> b = Stream.builder();
            b.accept(NEWLINE);

            // Iterate the complete list of meta-tag names to insert
            for (MetaTagName m : metaTags.keySet())
            {
                String contentAttributeValue = metaTags.get(m);
                checkForSingleQuote(contentAttributeValue);

                // Build the new node
                TagNode metaTN = new TagNode
                    ("<META NAME='" + m.name + "' CONTENT='" + contentAttributeValue + "'>");

                b.accept(metaTN);  b.accept(NEWLINE);
            }
            
            // Insert the meta-tag names into the page.  Put it at the top of the header,
            // just after <HEAD>

            Util.insertNodes(html, header.start + 1, b.build().toArray(HTMLNode[]::new));
        }


        // ****************************************************************************************
        // ****************************************************************************************
        // Insert **SPECIFIC** NAME/Property Meta-Tags
        // ****************************************************************************************
        // ****************************************************************************************


        /**
         * One common HTML Meta-Tag is the one which informs Google &amp; Yahoo (and all
         * search-engine sites) which of your pages you would like to be indexed by their search
         * engine, and which pages you would like to not be indexed.  Worrying about what Google
         * does and does not index may seem daunting, but this meta-tag can prevent certain
         * behaviors.
         *
         * <BR /><BR />The {@code 'ROBOTS'} Meta-Tag informs Search-Engines which pages on your
         * site should be indexed. This Meta-Tag serves a similar purpose to a {@code 'robots.txt'}
         * File.  It is generally used to prevent a Search-Engine from indexing individual pages,
         * while {@code 'robots.txt'} is used to prevent the search from indexing a whole site or
         * section of a site.
         *
         * <BR /><BR />A {@code 'ROBOTS'} Meta-Tag which instructs the Search-Engine Crawler not to
         * index a page, or follow any links on it, would be written as below.
         *
         * <DIV CLASS="HTML">{@code
         * <meta name="robots" content="noindex, nofollow" />
         * <meta name="robots" content="index, follow" />
         * }</DIV>
         *
         * @param html <EMBED CLASS='external-html' DATA-FILE-ID=FT_HTML_PARAM>
         *
         * @param index This is a {@code boolean}-Parameter that when set to {@code TRUE} will
         * force this method to place an {@code INDEX-String} into the finally-exported HTML
         * element.  If {@code FALSE} is passed, then a {@code NOINDEX-String} will be put into the
         * HTML-Tag.
         * 
         * @param follow This is also a {@code boolean}-Parameter.  When {@code TRUE} this will
         * force the method to put a {@code FOLLOW-String} into the finally-exported HTML-Tag.
         * When {@code FALSE}, then a {@code 'NOFOLLOW'} will be inserted.
         * 
         * @throws NodeNotFoundException <EMBED CLASS='external-html' DATA-FILE-ID=FT_NNF_EX>
         * 
         * @see #robotsMetaTag
         * @see #getAllRobots(Vector)
         * @see #getAllRobotsNOMHE(Vector)
         * @see TagNode
         */
        public static void insertRobots(Vector<HTMLNode> html, boolean index, boolean follow)
        {
            // Builds a robots meta tag.  These are used by google and search engines
            // <meta NAME=robots content='INSERT-CONTENT-STRING-HERE' />

            // The HTML Page must have a <HEAD> ... </HEAD> section, or an exception shall throw.
            DotPair header = TagNodeFindInclusive.first(html, "HEAD");

            if (header == null) throw new NodeNotFoundException
                (NO_HEADER_MESSAGE.replace("INSERT-STR", "Robots <META ... > Tag"));

            // Build a 'robots' TagNode
            TagNode robotsTN    = new TagNode(
                "<META NAME=robots CONTENT='" +
                (index ? "index" : "noindex") + ", " + (follow ? "follow" : "nofollow") +
                "' >"
            );

            // Insert the robots-tag into the page.
            // Put it at the top of the header, just after <HEAD>

            Util.insertNodes(html, header.start + 1, NEWLINE, robotsTN, NEWLINE);
        }

        /**
         * This will add an HTML Meta-Tag with a
         * <B STYLE='color: red;'>{@code <META NAME=robots>}</B>
         * 
         * <BR /><BR /><B CLASS=JDDescLabel>Validity Check Warning:</B>
         * 
         * <BR />This method avoids all presumed <I><B>validity check,</B></I> primarily because
         * making an attempt to identify what is absolutely correct or not-correct seems a little
         * far-fetched.
         * 
         * <BR /><BR />Although the number of actual values the {@code ROBOTS}-Attribute may
         * contain is very low, throwing a {@code MalformedHTMLException} for some errors, while
         * ignoring others was decided to best avoid during this method's development.
         * 
         * <BR /><BR /><B CLASS=JDDescLabel:>Aside:</B>
         * 
         * <BR />If a programmer were to pass both the {@link Robots#Follow} and the
         * {@link Robots#NoFollow} Enum-Constants, both of these tags would be inserted into an
         * HTML {@code 'robots'} Meta-Tag without any kind of warning or exception throw.
         * 
         * <BR /><BR />This, clearly, would be a faulty HTML directive, though.
         * 
         * @param html <EMBED CLASS='external-html' DATA-FILE-ID=FT_HTML_PARAM>
         * 
         * @param rArr This is an array of the Enumerated-Type {@link Robots}.  It may contain a
         * list of any number of the items available to add into an HTML Meta-Tag's
         * {@code ROBOTS}-Attribute.  If any of the array elements are null, they will be skipped
         * and ignored.
         *
         * @throws NodeNotFoundException <EMBED CLASS='external-html' DATA-FILE-ID=FT_NNF_EX>
         * 
         * @see #robotsMetaTag
         * @see #getAllRobots(Vector)
         * @see #insertRobots(Vector, boolean, boolean)
         * @see StrCSV#toCSV(Object[], IntTFunction, boolean, Integer)
         * @see DotPair
         */
        public static void insertRobots(Vector<HTMLNode> html, Robots... rArr)
        {
            // Builds a series-of-robots meta tag.  These are used by google and search engines
            // <meta NAME=robots content='INSERT-CONTENT-STRING-HERE' />

            // The HTML Page must have a <HEAD> ... </HEAD> section, or an exception shall throw.
            DotPair header = TagNodeFindInclusive.first(html, "HEAD");

            if (header == null) throw new NodeNotFoundException
                (NO_HEADER_MESSAGE.replace("INSERT-STR", "Robots <META ... > Tag"));

            String robotsStr = StrCSV.toCSV(rArr, (int i, Robots r) -> r.name, false, null);

            // Build the <META> TagNode
            TagNode robotsTN = new TagNode("<META NAME=robots CONTENT='" + robotsStr + "'>");

            // Insert the robots-tag into the page.  Put it at the top of the header, just
            // after <HEAD>

            Util.insertNodes(html, header.start + 1, NEWLINE, robotsTN, NEWLINE);
        }

        /**
         * Another common HTML {@code META}-Tag is the one that provides a brief description of
         * the page in question.  This method facilitates adding a Meta-Tag that contains two
         * attributes:
         * 
         * <BR /><BR /><OL CLASS=JDUL>
         * <LI> {@code NAME}-Attribute whose <B STYLE='color: red;'>value</B> must be
         *      {@code 'description'}
         *      </LI>
         * 
         * <LI> {@code CONTENT}-Attribute whose <B STYLE='color: red;'>value</B> should be a brief
         *      textual description of the content of the page
         *      </LI>
         * </OL>
         *
         * @param html <EMBED CLASS='external-html' DATA-FILE-ID=FT_HTML_PARAM>
         *
         * @param description This is a textual-description of the Web-Page to which this HTML
         * <B STYLE='color: red;'>{@code <META NAME=description CONTENT='...'}</B> Tag is being
         * added.  If Google or any of the other Internet Search Sites, return your Web-Page as a
         * part of a search-results, this description is usually used.
         * 
         * <BR /><BR />Furthermore, the key-words that are listed here are some-how (in a way that
         * is not-knownst to this programmer) used in indexing your particular page in the
         * search-algorithms.
         * 
         * @throws NodeNotFoundException <EMBED CLASS='external-html' DATA-FILE-ID=FT_NNF_EX>
         * 
         * @throws QuotesException <EMBED CLASS='external-html' DATA-PARAM=description
         *      DATA-FILE-ID=FT_Q_EX>
         *
         * @see #descriptionMetaTag
         * @see #hasDescription(Vector)
         * @see #checkForSingleQuote(String)
         * @see TagNode
         */
        public static void insertDescription(Vector<HTMLNode> html, String description)
        {
            // Meta-Tag for Descriptions.  This will be inserted into the HTML page.
            // <meta NAME=description content='INSERT-DESCRIPTION-OR-KEYWORDS-HERE'>

            checkForSingleQuote(description);

            // The HTML Page must have a <HEAD> ... </HEAD> section, or an exception shall throw.
            DotPair header = TagNodeFindInclusive.first(html, "HEAD");

            if (header == null) throw new NodeNotFoundException
                (NO_HEADER_MESSAGE.replace("INSERT-STR", "Description <META ... > Tag"));

            // Build the Meta Tag for a description to google and search engines
            TagNode metaTN = new TagNode
                ("<META NAME=description CONTENT='" + description + "'>");

            // Insert the description-tag into the page.  Put it at the top of the header,
            // just after <HEAD>

            Util.insertNodes(html, header.start + 1, NEWLINE, metaTN, NEWLINE);
        }

        /**
         * This will attempt to insert key-words into an HTML Meta-Tag.  This is usually used to
         * summarize-explain 'main-points' that a Web-Page author wants to make to any
         * search-engineer or any-listener on the internet about the Web-Page that includes such a
         * Meta-Tag.
         *
         * <BR /><BR /><B CLASS=JDDescLabel>Validity Checking:</B>
         * 
         * <BR />This method does a few minor validity checks regarding the content inside of a
         * description keyword.  All it does is look for things like White-Space and a few
         * punctuation rules.  If either of these problems occur inside any of the key-words
         * provided to the {@code 'keyWords'} Var-Args Parameter, then an
         * {@code IllegalArgumentException} is thrown.
         *
         * <BR /><BR /><B CLASS=JDDescLabel>Disallowed Punctuation:</B>
         * 
         * <BR />This list of disallowed punctuation marks for the key-words are as processed as
         * follows:
         * 
         * <DIV CLASS="SNIP">{@code
         * if (StrCmpr.containsOR
         *      (keyWord, ";", ",", "'", "\"", "!", "#", "<", ">",
         *      "(", ")", "*", "/", "\\")
         * )
         *     throw new IllegalArgumentException(...);
         * }</DIV>
         * 
         * @param html <EMBED CLASS='external-html' DATA-FILE-ID=FT_HTML_PARAM>
         * 
         * @param keyWords This is a list of germane key-words that help identify, indicate or
         * describe the content of the Web-Page in which they are placed.
         * 
         * @throws NodeNotFoundException <EMBED CLASS='external-html' DATA-FILE-ID=FT_NNF_EX>
         * 
         * @throws IllegalArgumentException If any of the key-words provided to the Java Var-Args
         * {@code 'keyWords'} parameter contain invalid punctuation characters, or white-space.
         *
         * @see #keyWordsMetaTag
         * @see #getAllKeyWords(Vector)
         * @see StringParse#hasWhiteSpace(String)
         * @see StrCmpr#containsOR(String, String[])
         * @see StrCSV#toCSV(String[], boolean, boolean, Integer)
         */
        public static void insertKeyWords(Vector<HTMLNode> html, String... keyWords)
        {
            // The meta-tag for key-words.  Search Engines look for these key-words when indexing
            // <meta NAME=keywords content='INSERT-COMMA-SEPARATED-KEYWORDS-HERE'>

            // The HTML Page must have a <HEAD> ... </HEAD> section, or an exception shall throw.
            DotPair header = TagNodeFindInclusive.first(html, "HEAD");

            if (header == null) throw new NodeNotFoundException
                (NO_HEADER_MESSAGE.replace("INSERT-STR", "KeyWords Meta-Tag"));

            for (String keyWord : keyWords) if (StringParse.hasWhiteSpace(keyWord)) 

                throw new IllegalArgumentException(
                    "You have tried to insert keywords into an HTML Meta-Tag KeyWord-{roperty, " +
                    "but unfortunately one of the words provided [" + keyWord + "] contains " +
                    "white-space.  This is not allowed here."
                );


            for (String keyWord : keyWords)

                if (StrCmpr.containsOR
                        (keyWord, ";", ",", "'", "\"", "!", "<", ">", "(", ")", "*", "/", "\\"))

                    throw new IllegalArgumentException(
                        "You have tried to insert keywords into an HTML Meta-Tag KeyWords-" +
                        "Property, but unfortunately one of the words provide [" + keyWord + "] " +
                        "contains error-prone punctuation, and cannot be used here."
                    );

            // All this does is build a list - Comma Separated values.
            String listAsString = StrCSV.toCSV(keyWords, true, false, null);

            // Build the TagNode, it will contain all key-words listed in the input var-args
            // String array

            TagNode metaTN = new TagNode("<META NAME=keywords CONTENT='" + listAsString + "'>");

            // Insert the tag into the page.  Put it at the top of the header, just after <HEAD>
            Util.insertNodes(html, header.start + 1, NEWLINE, metaTN, NEWLINE);
        }

        /**
         * This method will insert an "author" HTML Meta-Tag into the
         * <B STYLE='color: red;'>{@code <HEAD> ... </HEAD>}</B> section of this page.
         * 
         * @param html <EMBED CLASS='external-html' DATA-FILE-ID=FT_HTML_PARAM>
         * @param author This is the author of this Web-Page.
         * @throws NodeNotFoundException <EMBED CLASS='external-html' DATA-FILE-ID=FT_NNF_EX>
         * 
         * @throws QuotesException If the author's name prevents the HTML-Engine from building any
         * version of an {@code AUTHOR} Meta-Tag.  This will happen, certainly, if the author's
         * name-{@code String} contains <I><B>both</B></I> a single <I><B>and</B></I> a double
         * quote.
         * 
         * <BR /><BR />Choose either the single-quote, or the double.  Do not use both, or this
         * exception will throw.
         *
         * <BR /><BR /><B><SPAN STYLE="color: red;">MOST IMPORTANT</B></SPAN> Most author's names
         * don't have any quotes at all!  Checking for these things prevents unexplainable
         * exceptions later on.
         *
         * @see #authorMetaTag
         * @see #hasAuthor(Vector)
         * @see SD
         * @see DotPair
         */
        public static void insertAuthor(Vector<HTMLNode> html, String author)
        {
            // The 'Author' Meta tag shall be inserted into the html page.
            // <meta NAME=author content='INSERT-AUTHOR-NAME-HERE'>

            // The HTML Page must have a <HEAD> ... </HEAD> section, or an exception shall throw.
            DotPair header = TagNodeFindInclusive.first(html, "HEAD");

            if (header == null) throw new NodeNotFoundException
                (NO_HEADER_MESSAGE.replace("INSERT-STR", "author meta-tag"));

            if ((author.indexOf("'") != -1) && (author.indexOf("\"") != -1))

                throw new QuotesException(
                    "The author string provided here contains both a single-quote and a double-" +
                    "quote, but this cannot be inserted into any HTML-Tag.  Please remove " +
                    "one or the other."
                );

            // Use the more complicated TagNode constructor to build the "author" tag.
            SD          quote   = (author.indexOf("'") == -1) ? SD.SingleQuotes : SD.DoubleQuotes;
            Properties  p       = new Properties();

            p.put("NAME", "author");
            p.put("CONTENT", author);

            // This constructor accepts a properties instance.
            TagNode authorTN = new TagNode("META", p, quote, true);

            // Insert the tag into the page.  Put it at the top of the header, just after <HEAD>
            Util.insertNodes(html, header.start + 1, NEWLINE, authorTN, NEWLINE);
        }


        // ****************************************************************************************
        // ****************************************************************************************
        // Insert HTTP-EQUIV Meta-Tags
        // ****************************************************************************************
        // ****************************************************************************************


        /**
         * This does a very simple insertion of an HTML Meta-Tag for a specific type,
         * Meta-Tags that have a {@code HTTP-EQUIV}-Attribute paired with a
         * {@code CONTENT}-Attribute.
         * 
         * @param html <EMBED CLASS='external-html' DATA-FILE-ID=FT_HTML_PARAM>
         * 
         * @param httpEquiv This is the property that is passed using the
         * {@code HTTP-EQUIV}-Attribute.
         * 
         * @param contentAttributeValue This is the value that will be used to set the
         * {@code CONTENT}-Attribute.
         * 
         * @throws NodeNotFoundException <EMBED CLASS='external-html' DATA-FILE-ID=FT_NNF_EX>
         * 
         * @throws QuotesException <EMBED CLASS='external-html' DATA-PARAM=contentAttributeValue
         *      DATA-FILE-ID=FT_Q_EX>
         *
         * @see #metaTagHTTPEquiv
         * @see #getHTTPEquiv(Vector, String)
         * @see DotPair
         * @see TagNode
         */
        public static void insertHTTPEquiv
            (Vector<HTMLNode> html, String httpEquiv, String contentAttributeValue)
        { 
            // Builds and inserts a TagNode HTML Element that looks like:
            // <meta http-equiv='INSERT-HTTP-EQUIV-STRING-HERE'
            //      content='INSERT-CONTENT-STRING-HERE' >

            // Single Quotes are used, so the attribute-value may not contain single quotes.
            checkForSingleQuote(contentAttributeValue);
    
            // The HTML Page must have a <HEAD> ... </HEAD> section, or an exception shall throw.
            DotPair header = TagNodeFindInclusive.first(html, "HEAD");

            if (header == null) throw new NodeNotFoundException
                (NO_HEADER_MESSAGE.replace("INSERT-STR", "<META HTTP-EQUIV=... CONTENT=...> Tag"));

            // Build a <META> tag, as in the comment above
            TagNode metaTN  = new TagNode
                ("<META HTTP-EQUIV='" + httpEquiv + "' CONTENT='" + contentAttributeValue + "'>");

            // Insert the meta-tag into the page.  Put it at the top of the header,
            // just after <HEAD>

            Util.insertNodes(html, header.start + 1, NEWLINE, metaTN, NEWLINE);
        }

        /**
         * The method will insert a {@code UTF-8} Meta-Tag that identifies the HTML-Page to any
         * Web-Browser that attempts to render its content as containing Foreign-Language
         * Characters, Emoji's &amp; other non-{@code ASCII} Glyphs.
         * 
         * <BR /><BR />{@code UTF-8} text utilizes/makes-use-of characters in a higher
         * {@code 'byte-range'} than the traditional <I>single-byte (256 different-characters) ASCII</I>
         * Character-Set.  {@code UTF-8} allows for Chinese, Japanese and just about every variant of
         * language in the rest of the world.
         *
         * @param html <EMBED CLASS='external-html' DATA-FILE-ID=FT_HTML_PARAM>
         * 
         * @throws NodeNotFoundException <EMBED CLASS='external-html' DATA-FILE-ID=FT_NNF_EX>
         * 
         * @see #hasUTF8MetaTag(Vector)
         * @see #UTF8MetaTag
         * @see TagNode
         * @see DotPair
         */
        public static void insertUTF8MetaTag(Vector<HTMLNode> html)
        {
            // Meta-Tag to assert that the UTF-8 Charset is being used:
            // <meta http-equiv='Content-Type' content='text/html; charset=utf-8' />

            // The HTML Page must have a <HEAD> ... </HEAD> section, or an exception shall throw.
            DotPair header = TagNodeFindInclusive.first(html, "HEAD");

            if (header == null) throw new NodeNotFoundException
                (NO_HEADER_MESSAGE.replace("INSERT-STR", "UTF-8 <META> Tag"));

            // Insert the UTF-8 tag into the page.  Put it at the top of the header, just
            // after <HEAD>

            Util.insertNodes(html, header.start + 1, NEWLINE, new TagNode(UTF8MetaTag), NEWLINE);
        }


        // ****************************************************************************************
        // ****************************************************************************************
        // ITEMPROP Meta-Tags
        // ****************************************************************************************
        // ****************************************************************************************


        /**
         * This does a very simple insertion of an HTML Meta-Tag for a specific type,
         * Meta-Tags that have an {@code ITEMPROP}-Attribute paired with a
         * {@code CONTENT}-Attribute set.
         * 
         * @param html <EMBED CLASS='external-html' DATA-FILE-ID=FT_HTML_PARAM>
         * 
         * @param itemProp This is a property that is passed via the {@code ITEMPROP}-Attribute
         * 
         * @param contentAttributeValue This is the value that will be used to set the
         * {@code CONTENT}-Attribute
         * 
         * @throws NodeNotFoundException <EMBED CLASS='external-html' DATA-FILE-ID=FT_NNF_EX>
         * 
         * @throws QuotesException <EMBED CLASS='external-html' DATA-PARAM=contentAttributeValue
         *      DATA-FILE-ID=FT_Q_EX>
         *
         * @see #metaTagItemProp
         * @see #getItemProp(Vector, String)
         * @see DotPair
         * @see TagNode
         */
        public static void insertItemProp
            (Vector<HTMLNode> html, String itemProp, String contentAttributeValue)
        { 
            // Builds and inserts a TagNode HTML Element that looks like:
            // <meta itemprop='INSERT-ITEMPROP-STRING-HERE' content='INSERT-CONTENT-STRING-HERE' >

            // Single Quotes are used, so the attribute-value may not contain single quotes.
            checkForSingleQuote(contentAttributeValue);
    
            // The HTML Page must have a <HEAD> ... </HEAD> section, or an exception shall throw.
            DotPair header = TagNodeFindInclusive.first(html, "HEAD");

            if (header == null) throw new NodeNotFoundException
                (NO_HEADER_MESSAGE.replace("INSERT-STR", "<META ITEMPROP=... CONTENT=...> tag"));

            // Build a <META> tag, as in the comment above
            TagNode metaTN  = new TagNode
                ("<META ITEMPROP='" + itemProp + "' CONTENT='" + contentAttributeValue + "'>");

            // Insert the meta-tag into the page.  Put it at the top of the header,
            // just after <HEAD>

            Util.insertNodes(html, header.start + 1, NEWLINE, metaTN, NEWLINE);
        }

        /**
         * This method will find an HTML
         * <B STYLE='color: red;'>{@code <META ITEMPROP=... CONTENT=...>}</B> element whose
         * {@code ITEMPROP}-Attribute <B STYLE='color: red;'>value</B> is equal to the
         * {@code String}-parameter {@code 'itemProp'} (ignoring case).
         * 
         * <BR /><BR />After such an HTML {@code META}-Tag has been identified, its 
         * {@code CONTENT}-Attribute {@code String}-value will be subsequently queried, extracted
         * and returned by this method.
         * 
         * <BR /><BR /><B CLASS=JDDescLabel>Returning null, Gracefully:</B>
         * 
         * <BR />If the page provided does not have an HTML Meta-Tag with a {@code NAME}-Attribute
         * whose <B STYLE='color: red;'>value</B> is {@code 'name'} or if such an element is
         * identified, but that tag does not have a {@code CONTENT}-Attribute, then this method
         * will return null.
         * 
         * <BR /><BR /><B CLASS=JDDescLabel>Case Insensitive Comparison:</B>
         * 
         * <BR />Before the comparison is done with the {@code 'itemProp'} parameter, that
         * {@code String} is trimmed with {@code String.trim()}, and the comparison performed
         * <I>is done while ignoring case</I>.
         * 
         * @param html <EMBED CLASS='external-html' DATA-FILE-ID=HTMLVEC>
         * 
         * @param itemProp The Attribute-<B STYLE='color: red;'>name</B> of the
         * {@code ITEMPROP}-Attribute.
         * 
         * @return The {@code String}-value of the {@code CONTENT}-Attribute for a 
         * {@code META}-Tag whose {@code ITEMPROP}-Attribute is equal to the specified name
         * provided by parameter {@code 'itemProp'}.
         * 
         * <BR /><BR />If such information is not found on the page, then this method returns null.
         */
        public static String getItemProp(Vector<HTMLNode> html, String itemProp)
        {
            // Find the first <META ITEMPROP=... CONTENT=...> tag element where the name equals
            // the string-value provided by parameter 'itemProp'.

            TagNode tn = InnerTagGet.first
                (html, "META", "ITEMPROP", TextComparitor.EQ_CI, itemProp.trim());

            // If there are no <META ITEMPROP='itemProp' CONTENT=...> elements found on the page,
            // then this method returns null.

            if (tn == null) return null;

            // Return the string-value of the attribute 'content'.  Note that if this
            // attribute isn't available, this method shall return 'null', gracefully.

            return tn.AV("content");
        }


        // ****************************************************************************************
        // ****************************************************************************************
        // Open-Graph Meta-Tags
        // ****************************************************************************************
        // ****************************************************************************************


        /**
         * This will insert a single Open-Graph Meta-Tag into an HTML-Page.
         *
         * <BR /><BR /><B CLASS=JDDescLabel>Prepending <CODE>'og:'</CODE></B>
         * 
         * <BR />The name of the property <I><B>MUST NOT</B></I> begin with the characters
         * {@code "og:"}, because they will be prepended when the HTML
         * <B STYLE='color: red;'>{@code <META PROPERTY='...' CONTENT='...' />}</B> Tag is
         * instantiated.
         * 
         * <BR /><BR />Please review <I>exact</I> method body below.
         *
         * @param html <EMBED CLASS='external-html' DATA-FILE-ID=FT_HTML_PARAM>
         * 
         * @param ogProperty This is the name of the Open-Graph protocol property that is being
         * inserted.  Generally these are simple text-{@code String's} with alphanumeric-limited
         * names, or they are series of alphanumeric text-{@code String's}, separated by a period
         * {@code '.'} character.
         * 
         * @param ogValueAsStr If you look at the definition of the {@link #openGraphMetaTag} above
         * in this class, you may view all of the acceptable types that Open-Graph Properties may
         * use.
         * 
         * <BR /><BR />Whichever property or field that is being inserted, mostly, the field must
         * be converted to a {@code String} when being passed to this method.
         * 
         * @throws NodeNotFoundException <EMBED CLASS='external-html' DATA-FILE-ID=FT_NNF_EX>
         * 
         * @throws QuotesException <EMBED CLASS='external-html' DATA-PARAM1=ogProperty
         *      DATA-PARAM2=ogValueAsStr DATA-FILE-ID=FT_Q_EX_DOUBL>
         *
         * @see #openGraphMetaTag
         * @see #getAllOGMetaTags(Vector)
         * @see #checkForSingleQuote(String)
         * @see TagNode
         */
        public static void insertOGMetaTag
            (Vector<HTMLNode> html, String ogProperty, String ogValueAsStr)
        {
            // Open graph tag looks like this:
            // <meta property='og:INSERT-OG-PROPERTY-HERE' content='INSERT-OG-VALUE-HERE' />

            checkForSingleQuote(ogProperty);
            checkForSingleQuote(ogValueAsStr);

            // The HTML Page must have a <HEAD> ... </HEAD> section, or an exception shall throw.
            DotPair header = TagNodeFindInclusive.first(html, "HEAD");

            if (header == null) throw new NodeNotFoundException(
                NO_HEADER_MESSAGE.replace
                    ("INSERT-STR", "Open-Graph <META NAME='og:...' ...> Tag")
            );

            // Build the Open-Graph Meta Tag
            TagNode metaTN = new TagNode
                ("<META PROPERTY='og:" + ogProperty+ "' CONTENT='" + ogValueAsStr +"'>");

            // Insert the tag into the page.  Put it at the top of the header, just after <HEAD>
            Util.insertNodes(html, header.start + 1, NEWLINE, metaTN, NEWLINE);
        }

        /**
         * This will search any Vectorized HTML-Pge for
         * <B STYLE='color: red;'>{@code <META PROPERTY='og:...' CONTENT='...'>}</B> Tags, and
         * retrieve them for placement into a {@code java.util.Properties} table.
         * 
         * @param html <EMBED CLASS='external-html' DATA-FILE-ID=HTMLVEC>
         * 
         * @return This will return a Java {@code 'Properties'} Object, with all Open-Graph
         * properties saved inside.
         * 
         * @see #openGraphMetaTag
         * @see #insertOGMetaTag(Vector, String, String)
         * @see TagNode#AV(String)
         * @see InnerTagGet
         */
        public static Properties getAllOGMetaTags(Vector<? extends HTMLNode> html)
        {
            // InnerTagGet.all: Returns a vector of TagNode's that resemble:
            // <META property="og:..." ...>
            //
            // SW_CI_TRM: Check the 'property' Attribute-Value using a Case-Insensitive,
            //            'Starts-With' String-Comparison
            //            Trim the 'property' Attribute-Value String of possible leading & 
            //            trailing White-Space before performing the comparison.

            Vector<TagNode> v = InnerTagGet.all
                (html, "META", "PROPERTY", TextComparitor.SW_CI_TRM, "og:");

            Properties ret = new Properties();

            for (TagNode tn : v)
                ret.put(tn.AV("PROPERTY").substring(3), tn.AV("CONTENT"));

            return ret;
        }
    }
}