package com.srbenoit.xml;

import java.util.logging.Level;
import com.srbenoit.log.LoggedObject;

/**
 * A utility class to escape strings for use in XML (as an attribute value, for example) and
 * recover the original strings from such escaped string values.
 */
public final class XmlEscaper extends LoggedObject {

    /** a string builder used to construct output strings */
    private static final StringBuilder STR;

    static {
        STR = new StringBuilder(100);
    }

    /**
     * Private constructor to prevent instantiation.
     */
    private XmlEscaper() { }

    /**
     * Escapes a string for use in an XML attribute.
     *
     * @param   orig  the original string to escape
     * @return  the escaped string
     */
    public static String escape(final String orig) {

        char[] chars;

        chars = orig.toCharArray();

        synchronized (STR) {
            STR.setLength(0);

            for (char chr : chars) {

                switch (chr) {

                case '\"':
                    STR.append("&quot;");
                    break;

                case '\'':
                    STR.append("&apos;");
                    break;

                case '<':
                    STR.append("&lt;");
                    break;

                case '>':
                    STR.append("&gt;");
                    break;

                case '&':
                    STR.append("&amp;");
                    break;

                default:
                    STR.append(chr);
                    break;
                }
            }

            return STR.toString();
        }
    }

    /**
     * Unescapes a string that was processed by <code>escape</code>.
     *
     * @param   escaped  the string to unescape
     * @return  the original (unescaped) string
     */
    public static String unescape(final String escaped) {

        char[] chars;
        int len;
        int pos;

        chars = escaped.toCharArray();
        len = chars.length;

        synchronized (STR) {
            STR.setLength(0);

            pos = 0;

            while (pos < len) {

                if (chars[pos] == '&') {

                    if ((pos < (len - 5)) && (chars[pos + 1] == 'q') && (chars[pos + 2] == 'u')
                            && (chars[pos + 3] == 'o') && (chars[pos + 4] == 't')
                            && (chars[pos + 5] == ';')) {
                        STR.append('\"');
                        pos += 6;
                    } else if ((pos < (len - 5)) && (chars[pos + 1] == 'a')
                            && (chars[pos + 2] == 'p') && (chars[pos + 3] == 'o')
                            && (chars[pos + 4] == 's') && (chars[pos + 5] == ';')) {
                        STR.append('\'');
                        pos += 6;
                    } else if ((pos < (len - 3)) && (chars[pos + 1] == 'l')
                            && (chars[pos + 2] == 't') && (chars[pos + 3] == ';')) {
                        STR.append('<');
                        pos += 4;
                    } else if ((pos < (len - 3)) && (chars[pos + 1] == 'g')
                            && (chars[pos + 2] == 't') && (chars[pos + 3] == ';')) {
                        STR.append('>');
                        pos += 4;
                    } else if ((pos < (len - 4)) && (chars[pos + 1] == 'a')
                            && (chars[pos + 2] == 'm') && (chars[pos + 3] == 'p')
                            && (chars[pos + 4] == ';')) {
                        STR.append('&');
                        pos += 5;
                    } else {
                        LOG.warning("Invalid escape sequence detected");
                        STR.append(chars[pos]);
                        pos++;
                    }
                } else {
                    STR.append(chars[pos]);
                    pos++;
                }
            }

            return STR.toString();
        }
    }

    /**
     * Main method to exercise the methods in this class.
     *
     * @param  args  command-line arguments (ignored)
     */
    public static void main(final String... args) {

        String orig;
        String escaped;
        String test;

        orig = "\"'&<>&'>'<&>\"\"\"'''\"";
        escaped = XmlEscaper.escape(orig);
        test = XmlEscaper.unescape(escaped);

        LOG.log(Level.FINE, "Original:  [{0}]", orig);
        LOG.log(Level.FINE, "Escaped:   [{0}]", escaped);
        LOG.log(Level.FINE, "Recovered: [{0}]", test);
    }
}
