001package Torello.HTML.NodeSearch;
002
003import java.util.regex.*;
004import java.util.stream.Stream;
005
006import java.util.function.Predicate;
007
008import Torello.HTML.TagNode;
009
010/**
011 * An exception used for problems generated by either HTML-Tag <B>in-line</B> CSS
012 * <CODE>STYLE='&#46;&#46;&#46;'</CODE> attributes, or by explicitly declared CSS
013 * <CODE>&lt;STYLE TYPE='text/css'&gt;</CODE> <B>style-blocks</B>.
014 * 
015 * <BR /><BR />
016 * This class does some passed-parameter checking for the HTML-Search routines.  If one is
017 * using the {@code class TextComparitor}, it is important to remember that it usually works in 
018 * coordination with Java's var-args syntax which allows anywhere from {@code '0'} to
019 * {@code 'n' String's}.  This class of exception will be thrown if any one of the
020 * {@code String's} passed to the var-args parameter with the {@code TextComparitor} them has an
021 * invalid CSS Token according to the definition of valid CSS Naming-Convention Tokens.
022 *
023 * <BR /><BR /><B CLASS=JDDescLabel>NOTE:</B>
024 * 
025 * <BR />This exception test is only performed when a {@link TextComparitor} is used with one of
026 * the {@link TagNode} CSS {@code 'class'} getter and setter methods, for example:
027 * {@link TagNode#setCSSClasses​(SD, boolean, String[])}
028 *
029 * <BR /><BR /><EMBED CLASS='external-html' DATA-FILE-ID=EXPM>
030 */
031public class CSSStrException extends TCCompareStrException
032{
033    /** <EMBED CLASS='external-html' DATA-FILE-ID=SVUIDEX>  */
034    public static final long serialVersionUID = 1;
035
036    /**
037     * This regular-expression can be used to identify valid versus invalid CSS Class-Names, and
038     * also CSS ID-Names. In English, this regular-expression says: A valid name should start with
039     * an underscore (_), a hyphen (-) or a letter (a-z) which is followed by any numbers, hyphens,
040     * underscores, letters. A name should be at least two characters long. Cannot start with a
041     * digit, two hyphens or a hyphen followed by a number.
042     */
043    public static final Pattern VALID_CSS_CLASS_OR_NAME_TOKEN = 
044        Pattern.compile("^[_\\-a-zA-Z]+[_\\-a-zA-Z0-9]*$");
045
046    /**
047     * Regular-Expression as {@code Predicate<String>}
048     * @see #VALID_CSS_CLASS_OR_NAME_TOKEN
049     */
050    public static final Predicate<String> VALID_CSS_CLASS_OR_NAME_TOKEN_PRED =
051        VALID_CSS_CLASS_OR_NAME_TOKEN.asPredicate();
052
053    /**
054     * This constructor just calls the line: {@code super(message, compareStr, i);}
055     * 
056     * @param message the detail message.
057     * 
058     * @param compareStr This <I>SHOULD BE</I> the list of compare-strings that were passed to a
059     * TextComparitor that may have contained a null value, or was empty.  It is important not to
060     * pass null for this parameter, because checking the input to an exception's constructor is
061     * not very wise... "Exceptions about Exceptions" is usually unintelligible to a programmer.
062     * 
063     * @param i This is the index into the compareStr var-args parameter list that cause the
064     * exception to throw.
065     */
066    public CSSStrException(String message, String[] compareStr, int i)
067    { super(message, compareStr, i); }
068
069    /**
070     * This constructor just calls the line: {@code super(message, cause, compareStr, i);}
071     * 
072     * @param message The detail message (which is saved for later retrieval by the
073     * {@code Throwable.getMessage()} method).
074     * 
075     * @param cause This is sometimes used to "chain" exceptions in multi-threaded or heavily I/O
076     * related code.
077     * 
078     * @param compareStr This <I>SHOULD BE</I> the list of compare-strings that were passed to a
079     * TextComparitor that may have contained a null value, or was empty.  It is important not to
080     * pass null for this parameter, because checking the input to an exception's constructor is
081     * not very wise... "Exceptions about Exceptions" is usually unintelligible to a programmer.
082     * 
083     * @param i This is the index into the compareStr var-args parameter list that cause the
084     * exception to throw.
085     */
086    public CSSStrException(String message, Throwable cause, String[] compareStr, int i)
087    { super(message, cause, compareStr, i); }
088
089    /**
090     * This will do a simple test of the compare-{@code String's} to make sure none of them are
091     * null, and that there are at least one {@code String} in the array.
092     *
093     * <!-- This Explanation has been Copied from Torello.Java.StrCmprException -->
094     * <BR /><BR /><B CLASS=JDDescLabel>Explanation:</B>
095     * 
096     * <BR />It is a subtle issue, but likely better to throw exceptions when one of the Varargs to
097     * a {@code String} comparison is {@code 'null'}.  Since there is likely no general-convention
098     * or agreement on what {@code null} in the presence of {@code logical AND, OR, NOT, XOR, NAND}
099     * really means, throwing a {@code StrCmprException} <I>when even one var-args string-parameter
100     * is {@code 'null'}</I> actually makes the most sense.
101     * 
102     * @param compareStr This should be the var-args parameter that was passed to a search method
103     * 
104     * @throws CSSStrException If any of the elements of Var-Args parameter {@code 'compareStr'}
105     * is null, or if the array is zero-length.
106     * 
107     * @see #VALID_CSS_CLASS_OR_NAME_TOKEN
108     * @see TCCompareStrException#check(String[])
109     */
110    public static void check(String... compareStr)
111    {
112        // Repackages the Exception into one with the name CSSStrException
113        try
114            { TCCompareStrException.check(compareStr); }
115        catch (TCCompareStrException e)
116        {
117            String[] tempV = new String[compareStr.length];
118            throw new CSSStrException(
119                e.getMessage() + "\nSee getCause() for details.",
120                e, e.compareStrVec.toArray(tempV), e.i
121            );
122        }
123
124        for (int i=0; i < compareStr.length; i++)
125
126            if (! VALID_CSS_CLASS_OR_NAME_TOKEN.matcher(compareStr[i]).find())
127
128                throw new CSSStrException(
129                    "One of the compare-strings passed to a search-method's var-args String " +
130                    "parameter 'compareStr': [" + compareStr[i] + "]\n" +
131                    "Did not pass the CSS Token-Naming Testing Regular-Expression: " +
132                    "[" + VALID_CSS_CLASS_OR_NAME_TOKEN.pattern() + "]\n" +
133                    "And this means it has been identified as an invalid CSS Token.  " +
134                    "This is not allowed here.\n" +
135                    "If you are using TagNode.cssClasses(), switch to TagNode.AV('class').\n" +
136                    "If you are using TextComparitor.CONTAINS_CSS_CLASS*, switch to " +
137                    "TextComparitor.EQ_CI_TRM",
138                    compareStr, i
139                );
140    }
141
142    /**
143     * This will do a simple test of the compare-{@code String's} to make sure none of them are
144     * null, and that there are at least one {@code String} in the array.
145     *
146     * <!-- This Explanation has been Copied from Torello.Java.StrCmprException -->
147     * <BR /><BR /><B CLASS=JDDescLabel>Explanation:</B>
148     * 
149     * <BR />It is a subtle issue, but likely better to throw exceptions when one of the Varargs to
150     * a {@code String} comparison is {@code 'null'}.  Since there is likely no general-convention
151     * or agreement on what {@code null} in the presence of {@code logical AND, OR, NOT, XOR, NAND}
152     * really means, throwing a {@code StrCmprException} <I>when even one var-args string-parameter
153     * is {@code 'null'}</I> actually makes the most sense.
154     * 
155     * @param compareStrs This should be the any instance of {@code Stream<String>}
156     * 
157     * @throws CSSStrException If any of the elements of the {@code compareStrs} stream are null,
158     * or if the stream is empty.
159     * 
160     * @see #VALID_CSS_CLASS_OR_NAME_TOKEN
161     * @see TCCompareStrException#check(String[])
162     */
163    public static Stream<String> check(Stream<String> compareStrs)
164    {
165        // Repackages the Exception into one with the name CSSStrException
166        String[] compareStr = compareStrs.toArray(String[]::new);
167
168        try
169            { TCCompareStrException.check(compareStr); }
170        catch (TCCompareStrException e)
171        {
172            String[] tempV = new String[compareStr.length];
173
174            throw new CSSStrException(
175                e.getMessage() + "\nSee getCause() for details.",
176                e, e.compareStrVec.toArray(tempV), e.i
177            );
178        }
179
180        for (int i=0; i < compareStr.length; i++) 
181
182            if (! VALID_CSS_CLASS_OR_NAME_TOKEN.matcher(compareStr[i]).find())
183
184                throw new CSSStrException(
185                    "One of the compare-strings passed to a search-method's var-args String " +
186                    "parameter 'compareStr': [" + compareStr[i] + "]\n" +
187                    "Did not pass the CSS Token-Naming Testing Regular-Expression: " +
188                    "[" + VALID_CSS_CLASS_OR_NAME_TOKEN.pattern() + "]\n" +
189                    "And this means it has been identified as an invalid CSS Token.  " +
190                    "This is not allowed here.\n" +
191                    "If you are using TagNode.cssClasses(), switch to TagNode.AV('class').\n" +
192                    "If you are using TextComparitor.CONTAINS_CSS_CLASS*, switch to " +
193                    "TextComparitor.EQ_CI_TRM",
194                    compareStr, i
195                );
196
197        return Stream.of(compareStr);
198        // The original stream is now empty!  We must re-build it!
199    }
200}