package org.xmlunit.matchers;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import org.w3c.dom.Element;
import org.xml.sax.InputSource;
import org.xmlunit.XMLUnitException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.HashMap;

import static org.hamcrest.CoreMatchers.both;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.Matchers.isEmptyString;
import static org.hamcrest.core.IsNot.not;
import static org.junit.Assert.assertThat;
import static org.xmlunit.matchers.HasXPathMatcher.hasXPath;

public class HasXPathMatcherTest {

    private DocumentBuilder db;

    @Before
    public void setup() throws ParserConfigurationException {
        DocumentBuilderFactory f = DocumentBuilderFactory.newInstance();
        f.setNamespaceAware(true);
        db = f.newDocumentBuilder();
    }

    @Test
    public void testXPathIsFoundInDocumentWithASingleOccurence() throws Exception {
        String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
                "<feed>" +
                "   <title>title</title>" +
                "   <entry>" +
                "       <title>title1</title>" +
                "       <id>id1</id>" +
                "   </entry>" +
                "</feed>";

        Element xmlRootElement = db.parse(
                new InputSource(new ByteArrayInputStream(xml.getBytes("utf-8")))).getDocumentElement();

        assertThat(xmlRootElement, hasXPath("entry/id"));
        assertThat(xmlRootElement, hasXPath("entry/title"));
        assertThat(xmlRootElement, not(hasXPath("entry/description")));
    }

    @Test
    public void testXPathIsFoundInStringWithASingleOccurence() throws Exception {
        String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
                "<feed>" +
                "   <title>title</title>" +
                "   <entry>" +
                "       <title>title1</title>" +
                "       <id>id1</id>" +
                "   </entry>" +
                "</feed>";


        assertThat(xml, hasXPath("//feed/entry/id"));
        assertThat(xml, hasXPath("//feed/entry/title"));
        assertThat(xml, not(hasXPath("//feed/entry/description")));
    }

    @Test
    public void testXPathIsFoundInStringWithMultipleOccurences() throws Exception {
        String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
                "<feed>" +
                "   <title>title</title>" +
                "   <entry>" +
                "       <title>title1</title>" +
                "       <id>id1</id>" +
                "   </entry>" +
                "   <entry>" +
                "       <title>title2</title>" +
                "       <id>id2</id>" +
                "   </entry>" +
                "</feed>";


        assertThat(xml, hasXPath("//feed/entry/id"));
        assertThat(xml, hasXPath("//feed/entry/title"));
        assertThat(xml, not(hasXPath("//feed/entry/description")));
    }

    @Test
    public void textXPathAttributeIsFound() throws Exception {
        String xml = "<a><b attr=\"abc\"></b></a>";
        assertThat(xml, hasXPath("//a/b/@attr"));

        try {
            assertThat(xml, hasXPath("//a/b[@attr=\"abcd\"]"));
            Assert.fail("Should throw AssertionError");
        }catch(AssertionError e){
            assertThat(e.getMessage(), containsString("XPath returned no results"));
        }
    }

    @Test
    public void testXPathIsFoundInDocumentWithNamespaceContextWithASingleOccurence() throws Exception {
        String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
                "<feed xmlns=\"http://www.w3.org/2005/Atom\">" +
                "   <title>title</title>" +
                "   <entry>" +
                "       <title>title1</title>" +
                "       <id>id1</id>" +
                "   </entry>" +
                "</feed>";

        HashMap<String, String> prefix2Uri = new HashMap<String, String>();
        prefix2Uri.put("atom", "http://www.w3.org/2005/Atom");
        Element xmlRootElement = db.parse(
                new InputSource(new ByteArrayInputStream(xml.getBytes("utf-8")))).getDocumentElement();

        assertThat(xmlRootElement, hasXPath("//atom:feed/atom:entry/atom:id").withNamespaceContext(prefix2Uri));
        assertThat(xmlRootElement, hasXPath("//atom:feed/atom:entry/atom:title").withNamespaceContext(prefix2Uri));
        assertThat(xmlRootElement,
                not(hasXPath("//atom:feed/atom:entry/atom:description").withNamespaceContext(prefix2Uri)));

    }

    /**
     * Really only tests there is no NPE.
     * @see "https://github.com/xmlunit/xmlunit/issues/81"
     */
    @Test(expected = AssertionError.class)
    public void canBeCombinedWithFailingMatcher() {
        assertThat("not empty", both(isEmptyString())
                   .and(hasXPath("count(//atom:feed/atom:entry")));
    }

    @Test
    public void canBeCombinedWithPassingMatcher() {
        String xml = "<a><b attr=\"abc\"></b></a>";
        assertThat(xml, both(not(isEmptyString()))
                   .and(hasXPath("//a/b/@attr")));
    }

    @Test
    public void usesDocumentBuilderFactory() throws Exception {
        DocumentBuilderFactory dFac = Mockito.mock(DocumentBuilderFactory.class);
        DocumentBuilder b = Mockito.mock(DocumentBuilder.class);
        Mockito.when(dFac.newDocumentBuilder()).thenReturn(b);
        Mockito.doThrow(new IOException())
            .when(b).parse(Mockito.any(InputSource.class));

        String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
                "<fruits>" +
                    "<fruit name=\"apple\"/>" +
                    "<fruit name=\"orange\"/>" +
                    "<fruit name=\"banana\"/>" +
                "</fruits>";
        try {
            assertThat(xml, hasXPath("//fruits/fruit").withDocumentBuilderFactory(dFac));
            Assert.fail("Expected exception");
        } catch (XMLUnitException ex) {
            Mockito.verify(b).parse(Mockito.any(InputSource.class));
        }
    }
}

@@ -6250,10 +6250,132 @@
 %7D%0A    %7D%0A
+%0A    @Test%0A    public void quotesCanBeMixed() %7B%0A        assertThat(%22%3Ca%3Eb'b%3C/a%3E%22, hasXPath(%22//a%5Btext()=%5C%22b'b%5C%22%5D%22));%0A    %7D%0A%0A
 %7D%0A

add an example for #110 as a unit test