/*   Copyright 2005 The Apache Software Foundation
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */
package misc.detailed;

import common.Common;
import jira.xmlbeans177.TestListDocument;
import jira.xmlbeans177A.TestListADocument;
import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlOptionCharEscapeMap;
import org.apache.xmlbeans.XmlOptions;
import org.junit.Test;

import java.io.File;
import java.util.HashMap;

import static org.junit.Assert.*;

public class CharEscapeTest extends Common
{
    static final String inputFile =
        XBEAN_CASE_ROOT + P + "misc" + P + "jira" + P + "xmlbeans_177.xml";
    static final String inputFile2 =
        XBEAN_CASE_ROOT + P + "misc" + P + "jira" + P + "xmlbeans_177a.xml";
    //static final String outputDir = OUTPUTROOT + P + "misc";
    //static final String outputFile1 = "xmlbeans_177_out1.xml";
    //static final String outputFile2 = "xmlbeans_177_out2.xml";

    static final String start = "<jira:testList xmlns:jira=\"http://jira/xmlbeans_177\">";
    static final String end = "</jira:testList>";
    static final String start2 = "<jira:testListA xmlns:jira=\"http://jira/xmlbeans_177a\">";
    static final String end2 = "</jira:testListA>";

    @Test
    public void testAddMapping() throws Exception
    {
        XmlOptionCharEscapeMap charEsc = new XmlOptionCharEscapeMap();
        // a brand new map does not contain predefined entities
        assertNull(charEsc.getEscapedString('<'));
        assertNull(charEsc.getEscapedString('>'));
        assertNull(charEsc.getEscapedString('&'));
        assertNull(charEsc.getEscapedString('\''));
        assertNull(charEsc.getEscapedString('"'));

        // test predefined entities
        charEsc.addMapping('<', XmlOptionCharEscapeMap.PREDEF_ENTITY);
        charEsc.addMapping('>', XmlOptionCharEscapeMap.PREDEF_ENTITY);
        charEsc.addMapping('&', XmlOptionCharEscapeMap.PREDEF_ENTITY);
        charEsc.addMapping('\'', XmlOptionCharEscapeMap.PREDEF_ENTITY);
        charEsc.addMapping('"', XmlOptionCharEscapeMap.PREDEF_ENTITY);
        assertEquals("&lt;", charEsc.getEscapedString('<'));
        assertEquals("&gt;", charEsc.getEscapedString('>'));
        assertEquals("&amp;", charEsc.getEscapedString('&'));
        assertEquals("&apos;", charEsc.getEscapedString('\''));
        assertEquals("&quot;", charEsc.getEscapedString('"'));

        // additions can be overwritten
        charEsc.addMapping('<', XmlOptionCharEscapeMap.HEXADECIMAL);
        assertEquals("&#x3c;", charEsc.getEscapedString('<'));
        charEsc.addMapping('<', XmlOptionCharEscapeMap.DECIMAL);
        assertEquals("&#60;", charEsc.getEscapedString('<'));

        // test some other characters
        assertNull(charEsc.getEscapedString('A'));
        charEsc.addMapping('A', XmlOptionCharEscapeMap.DECIMAL);
        charEsc.addMapping('B', XmlOptionCharEscapeMap.HEXADECIMAL);
        assertEquals("&#65;", charEsc.getEscapedString('A'));
        assertEquals("&#x42;", charEsc.getEscapedString('B'));

        // non-xml entities cannot be escaped as predefined entities
        try
        {
            charEsc.addMapping('C', XmlOptionCharEscapeMap.PREDEF_ENTITY);
            fail("should have thrown an exception");
        }
        catch (Exception e)
        {
            //System.out.println(e.getMessage());
            assertTrue(e instanceof XmlException);
            String msg = "the PREDEF_ENTITY mode can only be used for the following characters: <, >, &, \" and '";
            assertTrue(e.getMessage().endsWith(msg));
        }
    }

    @Test
    public void testAddMappings() throws Exception
    {
        XmlOptionCharEscapeMap charEsc = new XmlOptionCharEscapeMap();
        // non-xml entities cannot be escaped as predefined entities
        try
        {
            charEsc.addMappings('A', 'Z', XmlOptionCharEscapeMap.PREDEF_ENTITY);
            fail("should have thrown an exception");
        }
        catch (Exception e)
        {
            assertTrue(e instanceof XmlException);
            String msg = "the PREDEF_ENTITY mode can only be used for the following characters: <, >, &, \" and '";
            assertTrue(e.getMessage().endsWith(msg));
        }

        // start char must be before end char
        try
        {
            charEsc.addMappings('a', 'Z', XmlOptionCharEscapeMap.HEXADECIMAL);
            fail("should have thrown an exception");
        }
        catch (Exception e)
        {
            assertTrue(e instanceof XmlException);
            String msg = "ch1 must be <= ch2";
            assertTrue(e.getMessage().endsWith(msg));
        }

        charEsc.addMappings('A', 'Z', XmlOptionCharEscapeMap.HEXADECIMAL);
        assertEquals("&#x41;", charEsc.getEscapedString('A'));
        assertEquals("&#x42;", charEsc.getEscapedString('B'));
        assertEquals("&#x43;", charEsc.getEscapedString('C'));
        assertEquals("&#x58;", charEsc.getEscapedString('X'));
        assertEquals("&#x59;", charEsc.getEscapedString('Y'));
        assertEquals("&#x5a;", charEsc.getEscapedString('Z'));

        // overwrite a mapping
        charEsc.addMapping('X', XmlOptionCharEscapeMap.DECIMAL);
        assertEquals("&#88;", charEsc.getEscapedString('X'));
        assertEquals("&#x59;", charEsc.getEscapedString('Y'));
        assertEquals("&#x5a;", charEsc.getEscapedString('Z'));
    }

    @Test
    public void testEscape1() throws Exception
    {
        File f = new File(inputFile);
        TestListDocument doc = TestListDocument.Factory.parse(f);

        // default behavior: without the character replacement map,
        // only the minimal, required characters are escaped
        String exp1 = start + "\n" +
            "  <test>This is a greater than sign: ></test>\n" +
            "  <test>This is a less than sign: &lt;</test>\n" +
            "  <test>This is a single quote: '</test>\n" +
            "  <test>This is a double quote: \"</test>\n" +
            "  <test>W.L.Gore &amp; Associates</test>\n" +
            "  <test>Character data may not contain the three-character sequence ]]&gt; with the > unescaped.</test>\n" +
            "  <test>In particular, character data in a CDATA section may not contain the three-character sequence ]]&amp;gt; with the > unescaped.</test>\n" +
            end;
        assertEquals(exp1, doc.xmlText().replaceFirst("(?s)<!--.*-->",""));

        XmlOptionCharEscapeMap charEsc = new XmlOptionCharEscapeMap();
        charEsc.addMapping('>', XmlOptionCharEscapeMap.PREDEF_ENTITY);
        XmlOptions opts = new XmlOptions();
        opts.setSaveSubstituteCharacters(charEsc);

        // escape '>' as predefined entity as well
        String exp2 = start + "\n" +
            "  <test>This is a greater than sign: &gt;</test>\n" +
            "  <test>This is a less than sign: &lt;</test>\n" +
            "  <test>This is a single quote: '</test>\n" +
            "  <test>This is a double quote: \"</test>\n" +
            "  <test>W.L.Gore &amp; Associates</test>\n" +
            "  <test>Character data may not contain the three-character sequence ]]&gt; with the &gt; unescaped.</test>\n" +
            "  <test>In particular, character data in a CDATA section may not contain the three-character sequence ]]&amp;gt; with the &gt; unescaped.</test>\n" +
            end;
        assertEquals(exp2, doc.xmlText(opts).replaceFirst("(?s)<!--.*-->",""));

        // escape block of chars as hexadecimal
        charEsc.addMappings('A', 'D', XmlOptionCharEscapeMap.HEXADECIMAL);
        // opts holds a reference to charEsc, so opts is updated
        String exp3 = start + "\n" +
            "  <test>This is a greater than sign: &gt;</test>\n" +
            "  <test>This is a less than sign: &lt;</test>\n" +
            "  <test>This is a single quote: '</test>\n" +
            "  <test>This is a double quote: \"</test>\n" +
            "  <test>W.L.Gore &amp; &#x41;ssociates</test>\n" +
            "  <test>&#x43;haracter data may not contain the three-character sequence ]]&gt; with the &gt; unescaped.</test>\n" +
            "  <test>In particular, character data in a &#x43;&#x44;&#x41;T&#x41; section may not contain the three-character sequence ]]&amp;gt; with the &gt; unescaped.</test>\n" +
            end;
        assertEquals(exp3, doc.xmlText(opts).replaceFirst("(?s)<!--.*-->",""));
        /*
        File od = new File(outputDir);
        od.mkdir();
        File of1 = new File(od, outputFile1);
        File of2 = new File(od, outputFile2);
        doc.save(of1);
        doc.save(of2, opts);
        */
    }

    @Test
    public void testEscape2() throws Exception
    {
        TestListDocument doc = TestListDocument.Factory.newInstance();
        TestListDocument.TestList testList = doc.addNewTestList();
        XmlOptionCharEscapeMap charEsc = new XmlOptionCharEscapeMap();
        // define a block
        charEsc.addMappings('a', 'z', XmlOptionCharEscapeMap.DECIMAL);
        // overwrite
        charEsc.addMapping('x', XmlOptionCharEscapeMap.HEXADECIMAL);
        charEsc.addMapping('>', XmlOptionCharEscapeMap.PREDEF_ENTITY);
        // pi
        charEsc.addMapping('\u03c0', XmlOptionCharEscapeMap.HEXADECIMAL);
        XmlOptions opts = new XmlOptions();
        opts.setSaveSubstituteCharacters(charEsc);
        HashMap prefixes = new HashMap();
        prefixes.put("http://jira/xmlbeans_177", "jira");
        opts.setSaveSuggestedPrefixes(prefixes);

        String[] testStrings = {"e < \u03c0", "\u03c0 > 3", "abcxyz"};
        testList.setTestArray(testStrings);

        String exp = start +
            "<test>&#101; &lt; &#x3c0;</test>" +
            "<test>&#x3c0; &gt; 3</test>" +
            "<test>&#97;&#98;&#99;&#x78;&#121;&#122;</test>" +
            end;
        assertEquals(exp, doc.xmlText(opts));
    }

    @Test
    public void testEscapeAttribute() throws Exception
    {
        File f = new File(inputFile2);
        TestListADocument doc = TestListADocument.Factory.parse(f);

        // default behavior: without the character replacement map,
        // only the minimal, required characters are escaped
        String exp1 = start2 + "\n" +
            "  <test a=\"This is a greater than sign: >\"/>\n" +
            "  <test a=\"This is a less than sign: &lt;\"/>\n" +
            "  <test a=\"This is a single quote: '\"/>\n" +
            "  <test a=\"This is a double quote: &quot;\"/>\n" +
            "  <test a=\"W.L.Gore &amp; Associates\"/>\n" +
            end2;
        assertEquals(exp1, doc.xmlText().replaceFirst("(?s)<!--.*-->",""));

        XmlOptionCharEscapeMap charEsc = new XmlOptionCharEscapeMap();
        charEsc.addMapping('>', XmlOptionCharEscapeMap.PREDEF_ENTITY);
        XmlOptions opts = new XmlOptions();
        opts.setSaveSubstituteCharacters(charEsc);

        // escape '>' as predefined entity as well
        String exp2 = start2 + "\n" +
            "  <test a=\"This is a greater than sign: &gt;\"/>\n" +
            "  <test a=\"This is a less than sign: &lt;\"/>\n" +
            "  <test a=\"This is a single quote: '\"/>\n" +
            "  <test a=\"This is a double quote: &quot;\"/>\n" +
            "  <test a=\"W.L.Gore &amp; Associates\"/>\n" +
            end2;
        assertEquals(exp2, doc.xmlText(opts).replaceFirst("(?s)<!--.*-->",""));

        // escape block of chars as hexadecimal
        charEsc.addMappings('A', 'D', XmlOptionCharEscapeMap.HEXADECIMAL);
        // opts holds a reference to charEsc, so opts is updated
        String exp3 = start2 + "\n" +
            "  <test a=\"This is a greater than sign: &gt;\"/>\n" +
            "  <test a=\"This is a less than sign: &lt;\"/>\n" +
            "  <test a=\"This is a single quote: '\"/>\n" +
            "  <test a=\"This is a double quote: &quot;\"/>\n" +
            "  <test a=\"W.L.Gore &amp; &#x41;ssociates\"/>\n" +
            end2;
        assertEquals(exp3, doc.xmlText(opts).replaceFirst("(?s)<!--.*-->",""));
    }
}
