package wstxtest.stream;

import java.io.*;

import javax.xml.stream.*;

import org.codehaus.stax2.XMLInputFactory2;
import org.codehaus.stax2.XMLStreamReader2;
import org.codehaus.stax2.validation.XMLValidationException;

/**
 * Set of unit tests that check that Woodstox support for Xml:id works
 * as expected.
 */
import com.ctc.wstx.api.ReaderConfig;

public class TestXmlId
    extends BaseStreamTest
{
    final static String XML_WITH_XMLID =
        "<root id='123'>"
        +"<leaf xml:id='abc' />"
        +"<leaf id='foobar'/>"
        +"<leaf xml:id='  _otherId ' />"
        +"</root id='123'>"
        ;

    final static String XML_WITH_XMLID_INVALID =
        "<!DOCTYPE root [\n"
        +"<!ELEMENT root ANY>\n"
        +"<!ATTLIST root xml:id CDATA #IMPLIED>\n"
        +"]><root />"
        ;

    public void testXmlIdEnabledNs()
        throws XMLStreamException
    {
        doTestXmlId(true, true, false); // xmlid enabled, non-coal
        doTestXmlId(true, true, true);  // xmlid enabled, coal
    }

    public void testXmlIdDisabledNs()
        throws XMLStreamException
    {
        doTestXmlId(false, true, false); // xmlid disabled, non-coal
        doTestXmlId(false, true, true); // xmlid disabled, coal
    }

    public void testXmlIdEnabledNonNs()
        throws XMLStreamException
    {
        doTestXmlId(true, false, false); // xmlid enabled, non-coal
        doTestXmlId(true, false, true);  // xmlid enabled, coal
    }

    public void testXmlIdDisabledNonNs()
        throws XMLStreamException
    {
        doTestXmlId(false, false, false); // xmlid disabled, non-coal
        doTestXmlId(false, false, true); // xmlid disabled, coal
    }

    /**
     * This unit test verifies that incorrect DTD attribute type for
     * xml:id causes a validation exception
     */ 
    public void testInvalidXmlIdNs()
        throws XMLStreamException
    {
        doTestInvalid(true, false);
        doTestInvalid(true, true);
    }

    public void testInvalidXmlIdNonNs()
        throws XMLStreamException
    {
        doTestInvalid(false, false);
        doTestInvalid(false, true);
    }

    public void testInvalidXmlIdDisabledNs()
        throws XMLStreamException
    {
        doTestInvalidDisabled(true, false);
        doTestInvalidDisabled(true, true);
    }

    public void testInvalidXmlIdDisabledNonNs()
        throws XMLStreamException
    {
        doTestInvalidDisabled(false, false);
        doTestInvalidDisabled(false, true);
    }

    /*
    /////////////////////////////////////
    //
    /////////////////////////////////////
     */

    private void doTestXmlId(boolean xmlidEnabled,
                             boolean nsAware, boolean coal)
        throws XMLStreamException
    {
        XMLStreamReader2 sr = getReader(XML_WITH_XMLID, xmlidEnabled, nsAware, coal);
        final String xmlidType = xmlidEnabled ? "ID" : "CDATA";

        // root:
        assertTokenType(START_ELEMENT, sr.next());
        assertEquals(1, sr.getAttributeCount());
        assertEquals("id", sr.getAttributeLocalName(0));
        assertEquals("CDATA", sr.getAttributeType(0));
        assertEquals(-1, sr.getAttributeInfo().getIdAttributeIndex());

        // leaf#1:
        assertTokenType(START_ELEMENT, sr.next());
        assertEquals(1, sr.getAttributeCount());
        assertEquals("abc", sr.getAttributeValue(0));
        if (xmlidEnabled) {
            assertEquals(0, sr.getAttributeInfo().getIdAttributeIndex());
        } else {
            assertEquals(-1, sr.getAttributeInfo().getIdAttributeIndex());
        }
        assertEquals(xmlidType, sr.getAttributeType(0));
        assertTokenType(END_ELEMENT, sr.next());

        // leaf#2:
        assertTokenType(START_ELEMENT, sr.next());
        assertEquals(1, sr.getAttributeCount());
        assertEquals("foobar", sr.getAttributeValue(0));
        assertEquals("id", sr.getAttributeLocalName(0));
        assertEquals(-1, sr.getAttributeInfo().getIdAttributeIndex());
        assertEquals("CDATA", sr.getAttributeType(0));
        assertTokenType(END_ELEMENT, sr.next());

        // leaf#3:
        assertTokenType(START_ELEMENT, sr.next());
        assertEquals(1, sr.getAttributeCount());
        assertEquals(xmlidType, sr.getAttributeType(0));
        if (xmlidEnabled) {
            assertEquals(0, sr.getAttributeInfo().getIdAttributeIndex());
        } else {
            assertEquals(-1, sr.getAttributeInfo().getIdAttributeIndex());
        }

        // also, should be normalized:
        if (xmlidEnabled) {
            assertEquals("_otherId", sr.getAttributeValue(0));
        } else {
            assertEquals("  _otherId ", sr.getAttributeValue(0));
        }
        assertTokenType(END_ELEMENT, sr.next());

        sr.close();
    }

    private void doTestInvalid(boolean nsAware, boolean coal)
        throws XMLStreamException
    {
        XMLStreamReader2 sr = getValidatingReader(XML_WITH_XMLID_INVALID, nsAware, coal);
        try {
            assertTokenType(DTD, sr.next());
            assertTokenType(START_ELEMENT, sr.next());
            fail("Expected a validation exception for invalid Xml:id attribute declaration");
        } catch (XMLValidationException vex) {
            //System.err.println("VLD exc -> "+vex);
        }
    }

    private void doTestInvalidDisabled(boolean nsAware, boolean coal)
        throws XMLStreamException
    {
        /* In non-validating mode, shouldn't matter: but just to make sure,
         * let's also disable xml:id processing
         */
        XMLStreamReader2 sr = getReader(XML_WITH_XMLID_INVALID, false, nsAware, coal);
        assertTokenType(DTD, sr.next());
        assertTokenType(START_ELEMENT, sr.next());
    }

    private XMLStreamReader2 getReader(String contents,
                                      boolean xmlidEnabled,
                                      boolean nsAware, boolean coal)
        throws XMLStreamException
    {
        XMLInputFactory2 f = getInputFactory();
        setSupportDTD(f, true);
        setValidating(f, false);
        setCoalescing(f, coal);
        setNamespaceAware(f, nsAware);
        f.setProperty(XMLInputFactory2.XSP_SUPPORT_XMLID,
                      xmlidEnabled
                      ? XMLInputFactory2.XSP_V_XMLID_TYPING
                      : XMLInputFactory2.XSP_V_XMLID_NONE);
        return constructStreamReader(f, contents);
    }

    private XMLStreamReader2 getValidatingReader(String contents,
                                                boolean nsAware, boolean coal)
        throws XMLStreamException
    {
        XMLInputFactory2 f = getInputFactory();
        setSupportDTD(f, true);
        setValidating(f, true);
        setCoalescing(f, coal);
        setNamespaceAware(f, nsAware);
        f.setProperty(XMLInputFactory2.XSP_SUPPORT_XMLID,
                      XMLInputFactory2.XSP_V_XMLID_TYPING);
        return constructStreamReader(f, contents);
    }
}
