001package Torello.HTML;
002
003import java.util.regex.*;
004
005/**
006 * This {@code Exception} is generated, usually, when a quote-within-quote problem has occurred
007 * inside HTML Attributes.
008 *   
009 * <BR /><BR />Attribute-<B STYLE="color: red;">values</B> cannot contain quotes, unless the
010 * inner-quotes do not match the outer-quotes.  Generally, there are not many HTML Inner-Tags that
011 * use quotes, other than the occasional {@code ALT="..."} text (in an image element), or possibly
012 * a Java-Script listener attribute.
013 * 
014 * <BR /><BR />However, this package performs quite a bit of {@code String}-operations, so 
015 * {@code String's} are checked when any method or constructor which builds instances of class
016 * {@link TagNode} containing any Inner-Tag <B STYLE="color: red;">Key-Value Pairs</B>.
017 * 
018 * <BR /><BR />In addition to checking for double-within-double-quotation mark problems (or 
019 * single-quote inside of a singly-quoted string), this {@code Exception} is also thrown if a user
020 * attempts to assign an attribute <B STYLE="color: red;">value</B> that uses the
021 * 'no-quotation-marks' version of attribute-<B STYLE="color: red;">value</B> pairs <I>when the
022 * value he is passing has any white-space, inside the
023 * <B STYLE="color: red;">value</B>-{@code String} at all.</I>
024 */
025public class QuotesException extends IllegalArgumentException
026{
027    /** <EMBED CLASS='external-html' DATA-FILE-ID=SVUIDEX> */
028    public static final long serialVersionUID = 1;
029
030    /** Constructs a {@code QuotesException} with no detail message. */
031    public QuotesException()
032    { super(); }
033
034    /**
035     * Constructs a {@code QuotesException} with the specified detail message.
036     * @param message the detail message.
037     */
038    public QuotesException(String message)
039    { super(message); }
040
041    /**
042     * Constructs a new exception with the specified detail message and cause.
043     * 
044     * <BR /><BR /><B CLASS=JDDescLabel>NOTE:</B>
045     * 
046     * <BR /><BR />The detail message associated with cause is not automatically incorporated into
047     * this exception's detail message.
048     * 
049     * @param message The detail message (which is saved for later retrieval by the
050     * {@code Throwable.getMessage()} method).
051     * 
052     * @param cause the cause (which is saved for later retrieval by the
053     * {@code Throwable.getCause()} method).  (A null value is permitted, and indicates that the
054     * cause is nonexistent or unknown.)
055     */
056    public QuotesException(String message, Throwable cause)
057    { super(message, cause); }
058
059    /**
060     * Constructs a new exception with the specified cause and a detail message of
061     * {@code (cause==null ? null : cause.toString())} (which typically contains the class and
062     * detail message of cause).  This constructor is useful for exceptions that are little more
063     * than wrappers for other throwables.
064     * 
065     * @param cause the cause (which is saved for later retrieval by the
066     * {@code Throwable.getCause()} method).  (A null value is permitted, and indicates that the
067     * cause is nonexistent or unknown.)
068     */
069    public QuotesException(Throwable cause)
070    { super(cause); }
071
072    /**
073     * This Regular-Expression {@code Pattern} is used internally for one particular scenario 
074     * involving a null quotes specifier.  It states that a {@code String} must conform to either
075     * single-quotes, double-quotes or no-quotes.
076     *
077     * <DIV CLASS="LOC">{@code
078     * // Throws Exception - No surrounding-quotes, has spaces
079     * check("This is an Attribute Value", null, "No Message");
080     *
081     * // Passes Inspection - No surrounding-quotes, but has no spaces.
082     * check("This-is-an-Attribute-Value", null, "No Message");
083     * 
084     * // Throws Exception - Has surrounding-quotes, but has quote-within-quote
085     * check("This is an\"Attribute\" Value", null, "No Message");
086     * 
087     * // Passes Inspection - Has surrounding-quotes, no quote-within-quote
088     * check("'This is an attribute value'", null, "No Message");
089     * }</DIV>
090     */
091    protected static final Pattern QUOTES_CHECKER =
092        Pattern.compile("^(\"[^\"]*\"|'[^']*'|[^'\"\\s]*)$");
093
094    /**
095     * The primary purpose of this {@code static} function is to generate a uniformly formatted
096     * error message when a "Quote within Quote" problem is identified.  If parameter
097     * {@code 'quotes'} is set to {@code SD.SingleQuotes}, then finding a single-quote within the
098     * input {@code String} will cause this {@code Exception} throw.  If quotes is set to
099     * {@code SD.DoubleQuotes}, then finding a double-quote in the input {@code String} will cause
100     * this {@code Exception} throw.  If parameter {@code 'quotes'} is null, then finding either
101     * will generate a {@code QuotesException}.
102     * 
103     * @param s The {@code String}-token to check
104     * 
105     * @param quotes The surrounding quotes used, or null if no quotes are being used.  If this
106     * value is null, then finding either quote in the {@code 's'} parameter will cause this
107     * {@code QuotesException} throw.
108     * 
109     * @param message A brief error message to report to the programmer.  If this is null, then it
110     * is not included.
111     * 
112     * @throws QuotesException If there is a "Quote within Quote" problem identified, as explained
113     * above.
114     */
115    public static void check(String s, SD quotes, String message)
116    {
117        if (quotes == null)
118        {
119            if (! QUOTES_CHECKER.matcher(s).find()) throw new QuotesException(
120                ((message == null)
121                    ? ""
122                    : (message.endsWith("\n") ? message : (message + "\n"))) +
123                "\nString: [" + s + "]\nis not being properly quoted."
124            );
125        }
126
127        else if (s.indexOf(quotes.quote) != -1) throw new QuotesException(
128            ((message == null)
129                ? ""
130                : (message.endsWith("\n") ? message : (message + "\n"))) +
131            "\nString: [" + s + "]\n" +
132            "contains a quote that matches the surrounding quotes: " + quotes.quote
133        );
134    }
135}