/* 
 * E-XML Library:  For XML, XML-RPC, HTTP, and related.
 * Copyright (C) 2002-2008  Elias Ross
 * 
 * genman@noderunner.net
 * http://noderunner.net/~genman
 * 
 * 1025 NE 73RD ST
 * SEATTLE WA 98115
 * USA
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * $Id$
 */

package net.noderunner.exml;

import java.io.BufferedWriter;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.List;

/**
 * This class is for unit testing the XmlReader class.
 *
 * @see XmlReader
 * @author Elias Ross
 * @version 1.0
 */
public class XmlReaderTest
	extends junit.framework.TestCase
{
	public XmlReaderTest(String name) {
		super(name);
	}

	StringWriter comment;
	StringWriter pi;
	String in;
	XmlReader reader;

	protected void setUp() {
		reader = new XmlReader();
	}

	public Writer handleComment() {
		return (comment = new StringWriter());
	}

	public Writer handleProcessingInstruction(String target) {
		pi = new StringWriter();
		pi.write(target + " ");
		return pi;
	}

	// test Attribute()
	public void testAttlist() 
		throws Exception
	{
		in = "<!ATTLIST doc a1 CDATA #IMPLIED>";
		reader.setReadString(in);
		reader.AttlistDecl();
		ElementRule rule = reader.getDtd().getElementRule("doc");
		AttributeRule ar;
		ar = (AttributeRule)rule.getAttributeRules().get(0);
		assertNotNull(ar);
	}

	public void testAttlistEnum() 
		throws Exception
	{
		in = "<!ATTLIST doc a1 (on | off|middle) 'on'>";
		reader.setReadString(in);
		reader.AttlistDecl();
		ElementRule rule = reader.getDtd().getElementRule("doc");
		AttributeRule ar;
		ar = (AttributeRule)rule.getAttributeRules().get(0);
		assertTrue(ar.allowedValue("on"));
		assertTrue(ar.allowedValue("off"));
		assertTrue(ar.allowedValue("middle"));
		assertTrue(!ar.allowedValue("fishy"));
	}

	// test Attribute()
	public void testAttribute() 
		throws Exception
	{
		in = "name \n= '&quot;&#x22;I&#x0020;love traffic %&amp; lights.  "
			+ "But &#79;nly when \"they\" are green.'";
		reader = new XmlReader(new StringReader(in));
		Attribute a = reader.Attribute(); 
		assertEquals("name", a.getName());
		assertTrue(a.getValue().startsWith("\"\"I love traffic %& lights.  But Only"));
	}

	// test Attribute2()
	public void testAttribute2() 
		throws Exception
	{
		in = "name \n= \"&quot;&#34;\"";
		reader = new XmlReader(new StringReader(in));
		Attribute a = reader.Attribute(); 
		assertEquals("two quotes", "\"\"", a.getValue());
	}

	public void testMissingStuff() 
		throws Exception
	{
		in = "<a ###/>";
		reader.setReadString(in);
		try {
			reader.element();
			fail("no attribute name"); 
		} catch (XmlException xre) {
		}

		in = "<a / >";
		reader.setReadString(in);
		try {
			reader.element();
			fail("expect />"); 
		} catch (XmlException xre) {
		}

		in = "<a'/>";
		reader.setReadString(in);
		try {
			reader.element();
			fail("expect <a -space-"); 
		} catch (XmlException xre) {
		}

		in = "</a |";
		reader.setReadString(in);
		try {
			reader.ETag();
			fail("expect <a></a>"); 
		} catch (XmlException xre) {
		}

		in = "</ >";
		reader.setReadString(in);
		try {
			reader.ETag();
			fail("expect <a></a>"); 
		} catch (XmlException xre) {
		}

		in = "<a></a?";
		reader.setReadString(in);
		try {
			reader.element();
			fail("expect <a></a>"); 
		} catch (XmlException xre) {
		}

		in = "<a><%</a>";
		reader.setReadString(in);
		try {
			reader.element();
			fail("expect <a>&lt;%</a>"); 
		} catch (XmlException xre) {
		}
	}
	/*
	// test Attribute3()
	public void testAttribute3() 
		throws Exception
	{
		in = "name \n= ' > ' ";
		reader = new XmlReader(new StringReader(in));
		try {
			Attribute a = reader.Attribute(); 
			fail("NO > in attributes"); 
		} catch (XmlException xre) {
		}
	}
	*/

	public void testPEReference()
		throws Exception
	{
		in = "'value %pef; &amp;'";
		reader = new XmlReader(new StringReader(in));
		try {
			reader.EntityValue(); 
			fail("Should fail reading bad PE");
		} catch (XmlException xre) {
		}
	}

	public void testPEReferenceResolve()
		throws Exception
	{
		reader.setReadString("%hello;");
		String e = "<!ENTITY Hello 'Bye'>";
		reader.getDtd().addParameterEntity("hello", new Entity(e));
		reader.extSubsetDecl();
		assertEquals("Bye", reader.getDtd().getEntity("Hello").getValue());
	}

	public void testContentspec()
		throws Exception
	{
		in = "<!ELEMENT doc ANY>";
		reader = new XmlReader(new StringReader(in));
		reader.elementdecl();
		in = "<!ELEMENT doc EMPTY>";
		reader = new XmlReader(new StringReader(in));
		reader.elementdecl();
		in = "<!ELEMENT doc ( #PCDATA )>";
		reader = new XmlReader(new StringReader(in));
		reader.elementdecl();
		in = "<!ELEMENT doc ( #PCDATA | e)* >";
		reader = new XmlReader(new StringReader(in));
		reader.elementdecl();
		try {
			in = "<!ELEMENT doc ( #PCDATA | e)>";
			reader = new XmlReader(new StringReader(in));
			reader.elementdecl();
			fail("Must be ( #PCDATA | e ) with )*");
		} catch (XmlException e) {
		}
	}

	public void testEntities()
		throws Exception
	{
		in = "<!DOCTYPE doc [" +
			"	<!ENTITY xml 'xml' >" +
			"	<!ENTITY % xml 'xml' >" +
			"   <!ENTITY foo '<foo/&#62;'>" +
			"	<!ENTITY xml2 '&xml;&xml;'>" +
			"	<!ENTITY xml3 '%xml;%xml;'>" +
			"]>";
		reader = new XmlReader(new StringReader(in));
		reader.doctypedecl();
		assertEquals("doctypedecl name", "doc", reader.getDtd().getName());
		Entity ent;
		ent = reader.getDtd().getEntity("xml2");
		assertEquals("&xml;&xml;", new String(ent.getValue()));
		ent = reader.getDtd().getEntity("xml3");
		assertEquals("xmlxml", new String(ent.getValue()));
		ent = reader.getDtd().getEntity("foo");
		assertEquals("<foo/>", new String(ent.getValue()));
	}

	public void testDuplicateElement()
		throws Exception
	{
		in = "<!DOCTYPE doc [" +
			"<!ELEMENT bob ANY>" +
			"<!ELEMENT bob ANY>" +
			"]>";
		reader = new XmlReader(new StringReader(in));
		try {
			reader.doctypedecl();
			fail("cannot declare element bob twice");
		} catch (XmlException e) {
		}
	}

	public void testEarlyAttlist()
		throws Exception
	{
		in = "<!DOCTYPE doc [" +
			"<!ATTLIST person gender CDATA #FIXED 'female'" +
			"                 code CDATA #FIXED 'white'>" +
			"<!ATTLIST person code CDATA #FIXED 'blue'>" +
			"<!ELEMENT person ANY>" +
			"]>";
		reader = new XmlReader(new StringReader(in));
		reader.doctypedecl();
		ElementRule rule = reader.getDtd().getElementRule("person");
		assertNotNull("not null", rule);
		ElementRule.AttributeRuleState aruleState 
			= new ElementRule.AttributeRuleState();
		Attribute a;
		a = new Attribute("gender", "female");
		AttributeRule arule;
		arule = rule.encounterAttribute(a, aruleState);
		assertEquals("fixed", true, arule.isFixed());
		assertEquals("fixed female", "female", arule.getValue());
		try {
			arule = rule.encounterAttribute(a, aruleState);
			fail("dupe attribute");
		} catch (AttributeRuleException e) { }
		a = new Attribute("code", "white");
		rule.encounterAttribute(a, aruleState);
		a = new Attribute("code", "blue");
		try {
			rule.encounterAttribute(a, aruleState);
			fail("not fixed attribute");
		} catch (AttributeRuleException e) { }
	}

	public void testConditionalSect()
		throws Exception
	{
		in = "<![ INCLUDE [" +
			"	<!ENTITY % PEnt1 '#PCDATA' >" +
			"	<!-- Comment -->" +
			"	<?pi ?>" +
			"	<!ENTITY Hello 'Hello World'>" +
			"]]>";
		reader = new XmlReader(new StringReader(in));
		reader.conditionalSect();
		Entity ent;
		ent = reader.getDtd().getEntity("Hello");
		assertEquals("Hello World", new String(ent.getValue()));
		ent = reader.getDtd().getParameterEntity("PEnt1");
		assertEquals("#PCDATA", new String(ent.getValue()));
	}

	public void testParamConditionalSect()
		throws Exception
	{
		in = "<![ %ignore; [" +
			"	<!ENTITY Hello 'Hello World'>" +
			"]]>";
		reader = new XmlReader(new StringReader(in));
		try {
			reader.conditionalSect();
			fail("param not defined");
		} catch (XmlException e) {
		}
		reader = new XmlReader(new StringReader(in));
		reader.getDtd().addParameterEntity("ignore", new Entity("IGNORE"));
		reader.conditionalSect();
		Entity ent = reader.getDtd().getEntity("Hello");
		assertEquals(null, ent);
	}

	public void testEntityValue()
		throws Exception
	{
		String val;
		in = "'value %pe; &amp;'";
		reader = new XmlReader(new StringReader(in));
		reader.getDtd().addParameterEntity("pe", new Entity("&"));
		val = reader.EntityValue(); 
		assertEquals("value & &amp;", val);
		in = "%pe;   ";
		// try 2
		reader = new XmlReader(new StringReader("%pe;"));
		reader.getDtd().addParameterEntity("pe", new Entity("&"));
		val = reader.EntityValue(); 
		assertEquals("&", val);
		// try 3
		reader = new XmlReader(new StringReader("&bad;"));
		try {
			val = reader.EntityValue(); 
			fail("Not an entityvalue");
		} catch (XmlException e) {
		}
	}

	public void testReference() 
		throws Exception
	{
		in = "blah";
		reader = new XmlReader(new StringReader(in));
		assertEquals(reader.Reference(), null);
	}

	public void testReference2() 
		throws Exception
	{
		in = "&blah ;";
		reader = new XmlReader(new StringReader(in));
		try {
			reader.Reference();
			fail("Should fail reading bad Reference");
		} catch (XmlException xre) {
		}
	}

	public void testReference3() 
		throws Exception
	{
		in = "&blah;";
		reader = new XmlReader(new StringReader(in));
		try {
			reader.Reference();
			fail("Should fail reading unknown Reference");
		} catch (XmlException xre) {
		}
	}

	public void testReference4() 
		throws Exception
	{
		// should succeed
		in = "&amp;";
		reader = new XmlReader(new StringReader(in));
		reader.Reference();
		assertEquals('&', reader.getScanner().read());
	}

	public void testCDSect() 
		throws Exception
	{
		// should succeed
		String in2 = "xx<!--COMMENT--><?php ?>xx";
		in = "<![CDATA[" + in2 + "]]>";
		reader = new XmlReader(new StringReader(in));
		StringWriter w = new StringWriter();
		reader.CDSect(w);
		assertEquals(in2, w.getBuffer().toString());
	}

	public void testCDSect2() 
		throws Exception
	{
		in = "<![[ ]]>";
		reader = new XmlReader(new StringReader(in));
		assertEquals(reader.CDSect(NullWriter.getInstance()), false);
	}

	public void testCharRef() 
		throws Exception
	{
		in = "&#x20;";
		reader = new XmlReader(new StringReader(in));
		reader.CharRef(); 
		char c = (char)reader.getScanner().read();
		assertEquals("Should get space", c, ' ');
	}

	public void testCharRef2() 
		throws Exception
	{
		in = "&#x2z;";
		reader = new XmlReader(new StringReader(in));
		try {
			reader.CharRef(); 
			fail("Should fail reading bad CharRef");
		} catch (XmlException xre) {
		}
	}

	public void testCharRef3() 
		throws Exception
	{
		in = "&#x";
		for (int i = 0; i < XmlReaderPrefs.MAX_REF_LEN; i++)
			in += "00";
		in += ";";
		reader = new XmlReader(new StringReader(in));
		try {
			reader.CharRef(); 
			fail("Should fail reading long CharRef");
		} catch (XmlException xre) {
		}
	}

	public void testCharRef4() 
		throws Exception
	{
		in = "&#x000";
		reader = new XmlReader(new StringReader(in));
		try {
			reader.CharRef(); 
			fail("Should fail reading bad CharRef");
		} catch (XmlException xre) {
		}
	}

	// tests STag()
	public void testSTag1() 
		throws Exception
	{
		in = "<barney   foo  = 'abcde' bar = \"fgh\">";
		reader = new XmlReader(new StringReader(in));
		Element barney = reader.STag();
		assertEquals(barney.getName(), "barney");
		List l = barney.getAttributes();
		assertEquals(l.size(), 2);
		assertEquals(barney.isOpen(), true);
	}

	// tests STag()
	public void testSTag1NS() 
		throws Exception
	{
		in = "<a:barney   a:foo  = 'abcde' a:bar = \"fgh\" xmlns:a='http://example.net/ns'>";
		reader = new XmlReader(new StringReader(in));
		Element barney = reader.STag();
		assertEquals("a:barney", barney.getName());
		assertEquals("barney", barney.getLocalName());
		assertEquals("a", barney.getPrefix());
		List l = barney.getAttributes();
		assertEquals(l.size(), 3);
		assertEquals(barney.isOpen(), true);
	}

	// tests STag()
	public void testSTag2() 
		throws Exception
	{
		in = "<bob le = 'xxx' bar = \"yyy\"  />";
		reader = new XmlReader(new StringReader(in));
		Element bob = reader.STag();
		List l = bob.getAttributes();
		assertEquals(bob.isOpen(), false);
		assertEquals(l.size(), 2);
	}

	// tests STag()
	public void testSTag3() 
		throws Exception
	{
		in = "not here <bob le = 'xxx' bar = \"yyy\"  />";
		reader = new XmlReader(new StringReader(in));
		Element bob = reader.STag();
		assertEquals(bob, null);
	}

	public void testSTag4() 
		throws Exception
	{
		in = "<bob blah><bob2 blah2>";
		reader = new XmlReader(new StringReader(in));
		try {
			reader.STag();
			fail("Should expect =");
		} catch (XmlException xre) {
		}
	}


	// tests ETag()
	public void testETag() 
		throws Exception
	{
		in = "</barney>";
		reader = new XmlReader(new StringReader(in));
		boolean found = reader.ETag(new Element("barney"));
		assertEquals(found, true);
	}

	// tests ETag()
	public void testETag2() 
		throws Exception
	{
		in = "</larry>";
		reader = new XmlReader(new StringReader(in));
		try {
			reader.ETag(new Element("barney"));
			fail("Should not have found </larry>");
		} catch (XmlException xre) {
		}
		in = "</123>";
		reader = new XmlReader(new StringReader(in));
		try {
			reader.ETag(new Element("123"));
			fail("Should not have found an ETag </123>");
		} catch (XmlException xre) {
		}
	}

	// tests ETag()
	public void testETag3() 
		throws Exception
	{
		in = " </bob>";
		reader = new XmlReader(new StringReader(in));
		boolean found = reader.ETag(new Element("barney"));
		assertEquals(found, false);
	}

	public void testXmlDecl()
		throws Exception
	{
		in = "<?pi ?>";	
		reader = new XmlReader(new StringReader(in));
		reader.Prolog(null);	
		// xml decl can't be pi, but ignore anyway
		in = "<?xml ?>";	
		reader = new XmlReader(new StringReader(in));
		try {
			reader.Prolog(null);	
			fail("xml decl must have version");
		} catch (XmlException e) {
		}
		in = "<?xml version='1.0' ?>";	
		reader = new XmlReader(new StringReader(in));
		reader.Prolog(null);	
		in = "<?xml version='1.0' ?>";	
		reader = new XmlReader(new StringReader(in));
		reader.TextDecl();	

		in = "<?xml version='1.0' standalone='YES'?>";	
		reader.setReadString(in);
		try {
			reader.XmlDecl();
			fail("xml standalone must be yes or no");
		} catch (XmlException e) {
		}

		in = "<?xml version='1.0' standalone='no'?>";	
		reader.setReadString(in);
		reader.XmlDecl();

		in = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
		reader.setReadString(in);
		reader.XmlDecl();

		in = " encoding=\"UTF-8\"";
		reader.setReadString(in);
		assertEquals("UTF-8", reader.EncodingDecl());

		in = " encoding = 'UTF_8'";
		reader.setReadString(in);
		assertEquals("UTF_8", reader.EncodingDecl());

		in = "<?xml version='1.0' standalone='yes'?>";	
		reader = new XmlReader(new StringReader(in));
		try {
			reader.TextDecl();
			fail("TextDecl must not have standalone tag");
		} catch (XmlException e) {
		}
		in = "<?Xml version='1.0' ?>";	
		reader = new XmlReader(new StringReader(in));
		try {
			reader.Prolog(null);
			fail("XmlDecl must have xml not Xml tag");
		} catch (XmlException e) {
		}
		in = "<?xmlversion='1.0' ?>";	
		reader.setReadString(in);
		try {
			reader.XmlDecl(false);
			fail("XmlDecl must have version");
		} catch (XmlException e) {
		}
		in = "<?xml version='aZ0.$$' ?>";	
		reader.setReadString(in);
		try {
			reader.Prolog(null);
			fail("bad XML version");
		} catch (XmlException e) {
			//System.out.println("XXXXXXXXXXXXXX " + e);
		}
	}

	static class DumbResolver implements SystemLiteralResolver {
		String got;
		String contents = "";
		public Reader resolve(String literal) {
			got = literal;
			return new StringReader(contents);
		}
	}

	public void testNames()
		throws Exception
	{
		reader = new XmlReader();
		reader.setReadString("a b c*");
		String s[] = (String[])reader.Names().toArray(new String[0]); 
		assertEquals(3, s.length);
		assertEquals("a", s[0]);
		assertEquals("b", s[1]);
		assertEquals("c", s[2]);
	}

	public void testCopyUntil()
		throws Exception
	{
		reader = new XmlReader();
		String s = "fruit]]fruit";
		reader.setReadString(s + new String(XmlTags.CDATA_END));
		StringWriter w = new StringWriter();
		reader.copyUntil(w, XmlTags.CDATA_END);
		assertEquals(s, w.toString());

		reader.setReadString(s);
		try {
			reader.copyUntil(w, XmlTags.CDATA_END);
			fail("EOF exception");
		} catch (XmlException e) { }
	}

	public void testToString()
		throws Exception
	{
		reader.setReadString("<doc/>");
		reader.toString();
		reader.document();
		reader.toString();
	}

	public void testIgnore()
		throws Exception
	{
		reader.Ignore();
	}

	public void testExtParsedEnt()
		throws Exception
	{
		reader.setReadString("<?xml version='1.0'?>fish");
		Element e = new Element("foo");
		reader.extParsedEnt(e);
		assertEquals("fish", e.getCharacterData());
	}

	public void testExtSubset()
		throws Exception
	{
		reader.setReadString("<?xml version='1.0'?><!ENTITY keanu 'woah'>");
		reader.extSubset();
		reader.setReadString("<?xml version='1.0'?><!ENTITY keanu 'woah'>");
		reader.extPE();
	}

	public void testGEPE()
		throws Exception
	{
		reader.setReadString("<!ENTITY keanu 'woah'>");
		reader.GEDecl();
		reader.setReadString("<!ENTITY % keanu 'woah'>");
		reader.PEDecl();
	}

	public void testNmtokens()
		throws Exception
	{
		reader = new XmlReader();
		reader.setReadString("a b -c:d");
		String s[] = (String[])reader.Nmtokens().toArray(new String[0]); 
		assertEquals(3, s.length);
		assertEquals("a", s[0]);
		assertEquals("b", s[1]);
		assertEquals("-c:d", s[2]);
		assertNull(reader.Nmtoken());
		reader.setReadString("a#");
		assertEquals("a", reader.Nmtoken());
	}

	public void testEncodingName()
		throws Exception
	{
		in = "UTF-16";
		reader.EncName(in);
		in = "UTF:16";
		try {
			reader.EncName(in);
			fail("bad encoding name");
		} catch (XmlException e) {
		}
		in = "azAZ09-_.";
		reader.EncName(in);
	}

	// tests doctypedecl()
	public void test_doctypedecl()
		throws Exception
	{
		in = "<!DOCTYPE  mybook SYSTEM \"mybook.dtd\">";
		reader.setReadString(in);
		DumbResolver slr;
		slr = new DumbResolver();
		reader.setResolver(slr);
		assertEquals(reader.doctypedecl(), true);
		assertEquals(slr.got, "mybook.dtd");

		in = "<!DOCTYPE  mybook SYSTEM \"mybook.dtd\">";
		slr.contents = " bad content "; 
		reader.setReadString(in);
		try {
			reader.doctypedecl();
			fail("crap in DTD");
		} catch (XmlException xre) {
		}

		reader.setReadString("<!DOCTYPE HTML [ >");
		try {
			assertEquals(reader.doctypedecl(), true);
			fail("should expect ] at end");
		} catch (XmlException xre) {
		}

		reader.setReadString("<!DOCTYPE HTML!>");
		try {
			assertEquals(reader.doctypedecl(), true);
			fail("should expect > at end");
		} catch (XmlException xre) {
		}

		reader.setReadString("<!DOCTYPE#SUCKS");
		try {
			assertEquals(reader.doctypedecl(), true);
			fail("should reject # in DOCTYPE");
		} catch (XmlException xre) {
		}
	}

	// tests Misc()
	public void testMisc()
		throws Exception
	{
		in = " \n \t  <!-- comment --><?pi ?>       ";
		reader = new XmlReader(new StringReader(in));
		assertEquals(true, reader.Misc(null));
		Document d = new Document();
		assertEquals(true, reader.Misc(d));
		assertTrue(d.getChildNodes().get(0) instanceof Comment);
		assertEquals(true, reader.Misc(d));
		assertTrue(d.getChildNodes().get(1) instanceof PI);
		assertEquals(true, reader.Misc(null));
		assertEquals(false, reader.Misc(null));
		assertEquals(false, reader.hasMoreData());
	}

	// tests Comment()
	public void testComment()
		throws Exception
	{
		String alpha = "abcdefghijklmnopqrstuvwxyz";
		in = "<!--" + alpha + "--><!--ackack--><!----><!-- skip --><!-- bob bob -->";
		reader = new XmlReader(new StringReader(in));
		Element e = new Element("root");
		Comment comment;
		e.appendChild(reader.comment(false));
		e.appendChild(reader.comment(false));
		e.appendChild(reader.comment(false));
		comment = (Comment)reader.comment(true);
		assertEquals(null, comment);
		e.appendChild(reader.comment(false));
		e.appendChild(reader.comment(false));

		List l = e.getChildNodes();
		comment = (Comment)l.get(0);
		assertEquals(comment.getData().toString(), alpha);
		comment = (Comment)l.get(1);
		assertEquals(comment.getData().toString(), "ackack");
		comment = (Comment)l.get(2);
		assertEquals(comment.getData().toString(), "");
		comment = (Comment)l.get(3);
		assertEquals(" bob bob ", comment.getData().toString());
		assertEquals(reader.hasMoreData(), false);
		// test bad comment
		in = "<!-- dashes -- here -->";
		reader = new XmlReader(new StringReader(in));
		try {
			reader.comment(false);
			fail("should reject -- in comment");
		} catch (XmlException xre) {
		}
	}

	// tests pi()
	public void testPI()
		throws Exception
	{
		String s = "echo \"hello <there>\"; "; 
		PI pi = new PI("php");
		pi.getWriter().write(s);
		in = pi.toString();

		Element e = new Element("test");
		reader = new XmlReader(new StringReader(in));
		e.appendChild(reader.pi(false));
		pi = (PI)e.getChild(0);
		assertEquals(pi.getName(), "php");
		assertEquals(pi.getData().toString(), s);

		reader.setReadString(in);
		assertEquals(null, reader.pi(true));

		reader.setReadString("<?444 ?>");
		try {
			reader.pi(false);
			fail("bad PI");
		} catch (XmlException ex) {
		}
	}

	public void testName() 
		throws Exception
	{
		in = "123";
		reader = new XmlReader(new StringReader(in));
		assertEquals("Should not be a name", reader.Name(), null);
	}

	public void testName2() 
		throws Exception
	{
		in = "_....-_:ab";
		reader = new XmlReader(new StringReader(in));
		assertEquals("Should be a name", reader.Name().toString(), in);
	}

	public void testName3() 
		throws Exception
	{
		in = "abc ";
		reader = new XmlReader(new StringReader(in));
		assertEquals("Should be a name", reader.Name().toString(), "abc");
	}

	public void testName4() 
		throws Exception
	{
		in = "";
		for (int i = 0; i < XmlReaderPrefs.MAX_NAME_LEN * 2; i++)
			in += "xyz";
		reader = new XmlReader(new StringReader(in));
		try {
			reader.Name();
			fail("Should fail reading long Name");
		} catch (XmlException xre) {
		}
		reader = new XmlReader(new StringReader(in));
		try {
			reader.Nmtoken();
			fail("Should fail reading long Nmtoken");
		} catch (XmlException xre) {
		}
		reader = new XmlReader(new StringReader('"' + in + '"'));
		try {
			reader.AttValue();
			fail("Should fail reading long AttValue");
		} catch (XmlException xre) {
		}
	}

	public void testPubidLiteral() 
		throws Exception
	{
		String s = "system";
		reader.setReadString("'" + s + "'");
		assertEquals(s, reader.PubidLiteral());
		reader.setReadString("' Make_Money_FAST <> '");
		try {
			reader.PubidLiteral();
			fail("bad PubidChar");
		} catch (XmlException e) { }
	}

	public void testSystemLiteral() 
		throws Exception
	{
		String s = "system";
		reader.setReadString("'" + s + "'");
		assertEquals(s, reader.SystemLiteral());

		reader.setReadString("?" + s + "?");
		try {
			reader.SystemLiteral();
			fail("bad quote");
		} catch (XmlException e) { }

		reader.setReadString("'" + s);
		try {
			reader.SystemLiteral();
			fail("bad quote");
		} catch (XmlException e) { }
	}

	public void testResolveAttValueExtEntity() 
		throws Exception
	{
		reader.setReadString("'&ext;'");
		Entity e = new Entity("publicID", "systemID");
		reader.getDtd().addEntity("ext", e);
		try {
			reader.AttValue();
			fail("external is not allowed");
		} catch (XmlException ex) { }

		reader.setReadString("'&notext;'");
		e = new Entity("value");
		reader.getDtd().addEntity("notext", e);
		String v = reader.AttValue();
		assertEquals("value", v);
	}

	public void testBadAttReference() 
		throws Exception
	{
		reader.setReadString("'&***'");
		try {
			reader.AttValue();
			fail("need name after &");
		} catch (XmlException ex) { }

		reader.setReadString("'&ent*'");
		Entity e = new Entity("value");
		reader.getDtd().addEntity("ent", e);
		try {
			reader.AttValue();
			fail("need ; after &ent");
		} catch (XmlException ex) { }
	}

	public void testXmlWriterReader() 
		throws Exception
	{
		String in;
		XmlReader reader;
		XmlWriter writer;
		StringWriter sw = new StringWriter();
		writer = new XmlWriter(new BufferedWriter(sw));
		writer.writeHeader("UTF-16");
		Element root = new Element("test");
		Element a = new Element("a");
		Element b = new Element("b");
		Element b2 = new Element("b2");
		Element c = new Element("c");
		writer.startElement(root);
			writer.startElement(a);
				writer.startElement(b);
				writer.endElement();
				writer.startElement(b2);
					writer.emptyElement(c);
						String shouldbe = "& should be &";
						writer.writeCData(shouldbe);
						writer.writeCData("<!-- should ignore -->");
						writer.writeCData("<?php echo 'ignore'; ?>");
						writer.writeCDSection("ignore & everything <? inside");
		writer.up(0);
		writer.flush();
		in = sw.getBuffer().toString();

		// test all read
		reader = new XmlReader(new StringReader(in));
		Element e = reader.document().getRootElement();
		assertEquals(e.getName(), "test");
			//              a               b             c                t
		Element celem = (Element)e.getChild(0);
		assertEquals(celem.getName(), "a");
		celem = (Element)celem.getChild(1);
		assertEquals(celem.getName(), "b2");
		celem = (Element)celem.getChild(0);
		assertEquals(celem.getName(), "c");
		assertEquals(celem.getNodeType(), Node.ELEMENT_NODE);

		Element be = (Element)e.getChild(0);
		be = (Element)be.getChild(1);
		assertTrue(be.getCharacterData().trim().startsWith(shouldbe));
	}

	public void testPE()
		throws Exception
	{
		String val = "<!ENTITY keanu \"&apos;woah&apos;\">";
		in = "<!DOCTYPE doc [" +
			"	<!ENTITY % keanu '" + val + "' >" +
			"	%keanu;" +
			"]>";
		reader = new XmlReader(new StringReader(in));
		reader.doctypedecl();
		Entity ent;
		ent = reader.getDtd().getParameterEntity("keanu");
		assertEquals(val, ent.getValue());
		ent = reader.getDtd().getEntity("keanu");
		assertEquals("&apos;woah&apos;", ent.getValue());
	}

	public void testPE2()
		throws Exception
	{
		String val = "<!ENTITY keanu \"woah\"";
		in = "<!DOCTYPE doc [" +
			"	<!ENTITY % keanu '" + val + "' >" +
			"	%keanu;>" +
			"]>";
		reader = new XmlReader(new StringReader(in));
		try {
			reader.doctypedecl();
			fail("Incomplete parameter entity");
		} catch (XmlException e) { }
	}

	public void testCheckReference()
		throws Exception
	{
		in = "<!DOCTYPE doc [" +
			"	<!ENTITY % keanu 'me & you' >" +
			"]>";
		reader = new XmlReader(new StringReader(in));
		try {
			reader.doctypedecl();
			fail("checkReference failed");
		} catch (XmlException e) { }
	}

	public void testCheckDoctypeName()
		throws Exception
	{
		in = "<!DOCTYPE doc [" +
			"	<!ELEMENT doc EMPTY>" +
			"]>";
		reader = new XmlReader(new StringReader(in + "<doc/>"));
		reader.document();
		reader = new XmlReader(new StringReader(in + "<docky/>"));
		try {
			reader.document();
			fail("root element failed");
		} catch (XmlException e) { }
	}

	public void testAttributeNaked()
		throws Exception
	{
		reader = new XmlReader();
		in = "name = happy";
		reader.setReadString(in);
		try {
			reader.Attribute();
			fail("no quotes");
		} catch (XmlException e) {
		}
	}

	public void testAttributeEOF()
		throws Exception
	{
		in = "<doc name='happy>";
		reader = new XmlReader(new StringReader(in));
		try {
			reader.document();
			fail("cannot have > in attribute value");
		} catch (XmlException xe) { }
		in = "name='";
		reader.setReadString(in);
		try {
			reader.Attribute();
			fail("EOF before quotes");
		} catch (XmlException e) {
		}
	}

	public void testAttributeExternalEntity()
		throws Exception
	{
		in = "<!DOCTYPE doc [" +
			"<!ENTITY e SYSTEM 'nul'> ]>" +
			"<doc a='&e;' />";
		reader = new XmlReader(new StringReader(in));
		try {
			reader.document();
			fail("checkReference failed");
		} catch (XmlException e) { }
	}

	public void testNotation()
		throws Exception
	{
		Notation n;
		in = "<!NOTATION note PUBLIC 'MY NOTE' '/tmp/blah'>";
		reader = new XmlReader(new StringReader(in));
		assertTrue("decl found", reader.NotationDecl());
		n = (Notation)reader.getDtd().getNotations().get("note");
		assertEquals("note pub", "MY NOTE", n.getPublicID());
		assertEquals("note sys", "/tmp/blah", n.getSystemID());
		in = "<!NOTATION note SYSTEM '/tmp/blah'>";
		reader = new XmlReader(new StringReader(in));
		reader.NotationDecl();
		n = (Notation)reader.getDtd().getNotations().get("note");
		assertEquals("note pub", null, n.getPublicID());

		in = "<!DOCTYPE doc [" +
			"<!NOTATION note SYSTEM 'blah' > " +
			"]> <doc/>";
		reader = new XmlReader(new StringReader(in));
		reader.document();
		n = (Notation)reader.getDtd().getNotations().get("note");
		assertEquals("note pub", null, n.getPublicID());
		assertEquals("note sys", "blah", n.getSystemID());

		in = "<!NOTATION note SYSTEM 'blah' ";
		reader.setReadString(in);
		try {
			reader.NotationDecl();
			fail("missing >");
		} catch (XmlException xe) { }

		in = "<!NOTATION $$$ SYSTEM 'blah'>";
		reader.setReadString(in);
		try {
			reader.NotationDecl();
			fail("bad name");
		} catch (XmlException xe) { }

		in = "<!NOTATION name$$$ SYSTEM 'blah'>";
		reader.setReadString(in);
		try {
			reader.NotationDecl();
			fail("space after name");
		} catch (XmlException xe) { }
	}

	public void testEntityExpansion()
		throws Exception
	{
		in = "<!DOCTYPE doc [ <!ELEMENT doc (e*)> " +
			"<!ELEMENT e EMPTY> <!ENTITY e SYSTEM 'whatever'> " +
			"]>" + 
			"<doc>&e;&e;</doc>";
		final String e = "<e/>";
		SystemLiteralResolver slr = new SystemLiteralResolver() {
			public Reader resolve(String s) { return new StringReader(e); }
		};
		reader = new XmlReader(new StringReader(in), new Dtd(), slr);
		reader.document();
	}

	public void testAttributeGT()
		throws Exception
	{
		in = "<doc name='<happy>' />";
		reader = new XmlReader(new StringReader(in));
		try {
			reader.document();
			fail("cannot have > in attribute value");
		} catch (XmlException xe) { }
	}

	public void testInternalPE()
		throws Exception
	{
		in = "<!DOCTYPE doc [" +
		"<!ELEMENT doc (#PCDATA)>" +
		"<!ENTITY % e \"<?xml version='1.0' encoding='UTF-8'?>\">" +
		"%e; ]> <doc></doc>";
		reader = new XmlReader(new StringReader(in));
		try {
			reader.document();
			fail("cannot have declaration in internal decl");
		} catch (XmlException xe) { }
	}

	public void testCheckReference2()
		throws Exception
	{
		in = "<!DOCTYPE doc [" +
			"	<!ENTITY % keanu 'me &12; you' >" +
			"]>";
		reader = new XmlReader(new StringReader(in));
		try {
			reader.doctypedecl();
			fail("checkReference failed");
		} catch (XmlException e) { }
	}

	public void testPESpace()
		throws Exception
	{
		in = "<!ENTITY% keanu 'me you' >";
		reader = new XmlReader(new StringReader(in));
		try {
			reader.EntityDecl();
			fail("testPESpace failed");
		} catch (XmlException e) { }
	}

	public void testIgnoreRecursive()
		throws Exception
	{
		in = "<![ IGNORE [ lalalala <![ what<ever  <![ what]ever2 ]]> lala ]]> ]]>";
		reader.setReadString(in);
		reader.conditionalSect();
		assertEquals(false, reader.hasMoreData());

		reader.setReadString(in);
		reader.extSubsetDecl();
		assertEquals(false, reader.hasMoreData());
	}

	public void testIgnoreRecursive2()
		throws Exception
	{
		in = "<![ IGNORE [ lalalala <![ whatever  <![ whatever2 lala ]]> ]]>";
		reader = new XmlReader(new StringReader(in));
		try {
			reader.conditionalSect();
			fail("EOF in ignoreSectContents");
		} catch (XmlException e) { }
	}

	public void testSetReader()
		throws Exception
	{
		Element e;
		in = "<doc>whatever</doc>";
		reader = new XmlReader(new StringReader(in));
		e = reader.document().getRootElement();
		assertEquals(e.getName(), "doc");
		in = "<doc2>whatever</doc2>";
		reader.setReader(new StringReader(in));
		e = reader.document().getRootElement();
		assertEquals(e.getName(), "doc2");
	}

	public void testGetResolver()
		throws Exception
	{
		XmlReader xr = new XmlReader();
		assertTrue(xr.getResolver() instanceof NullResolver);
	}

	public void testCharAndSpace()
		throws Exception
	{
		assertTrue(XmlReader.Char('a'));
		assertTrue(XmlReader.Char(0x09));
		assertTrue(XmlReader.Char(0x0a));
		assertTrue(XmlReader.Char(0x0d));
		assertTrue(XmlReader.Char(0xE000));
		assertTrue(XmlReader.FirstNameChar('a'));
		assertTrue(XmlReader.FirstNameChar((char)0x41));
		assertTrue(!XmlReader.Char('\u0000'));
		assertTrue(!XmlReader.Char('\b'));
		assertTrue(!XmlReader.Char('\uffff'));
		assertTrue(!XmlReader.PubidChar('"'));
		assertTrue(!XmlReader.PubidChar('&'));
		assertTrue(!XmlReader.PubidChar('\''));
		assertTrue(!XmlReader.PubidChar('<'));
		assertTrue(!XmlReader.PubidChar('>'));
		XmlReader xr = new XmlReader();
		xr.setReadString("\r\t\n ");
		xr.S();
		assertTrue("got it all", !xr.hasMoreData());
		assertTrue(!XmlReader.NameChar((char)174)); // (R) symbol
		assertTrue(XmlReader.Letter('a'));
		assertTrue(!XmlReader.Letter((char)174)); // (R) symbol
	}

	public void testStringPooling()
		throws Exception
	{
		XmlReader xr = new XmlReader();
		final String n = "Name";
		xr.setReadString(n);
		xr.getStringPool().add(n);
		String n2 =  xr.Name();
		assertEquals(n, n2);
		assertTrue("same object", n == n2);
	}

	public void testStringPoolingInit()
		throws Exception
	{
		Dtd dtd = new Dtd();
		final String n = "Name";
		final String s = "Socks";
		dtd.addElementRule(n, new ElementRule());
		XmlReader xr;
		xr = new XmlReader(new StringReader(n), dtd);
		assertTrue(xr.hasMoreData());
		assertTrue("same object", n == xr.Name());

		ElementRule er = new ElementRule();
		er.addAttributeRule(new AttributeRule(AttributeValueType.CDATA, s));
		dtd.addElementRule(n, er);
		xr = new XmlReader(new StringReader("<" + n + " " + s + "='x'/>"), dtd);
		Element e = xr.element();
		assertTrue("element name canon", n == e.getName());
		Attribute a = (Attribute)e.getAttributes().get(0);
		assertTrue("attr name canon", s == a.getName());
	}

	public void testSetReader2()
		throws Exception
	{
		reader = new XmlReader();
		try {
			reader.document();
			fail("EOF");
		} catch (XmlException e) {
		}

		Element e;
		in = "<doc>whatever</doc>";
		reader = new XmlReader();
		reader.setReadString(in);
		e = reader.document().getRootElement();
		assertEquals(e.getName(), "doc");
		in = "<doc2>whatever</doc2>";
		reader.setReadString(in);
		e = reader.document().getRootElement();
		assertEquals(e.getName(), "doc2");
	}
}
