// HTMLParser Library $Name: v1_6 $ - A java-based parser for HTML
// http://sourceforge.org/projects/htmlparser
// Copyright (C) 2004 Somik Raha
//
// Revision Control Information
//
// $Source: /cvsroot/htmlparser/htmlparser/src/org/htmlparser/tests/scannersTests/CompositeTagScannerTest.java,v $
// $Author: derrickoswald $
// $Date: 2004/07/31 16:42:32 $
// $Revision: 1.62 $
//
// 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.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//

package org.htmlparser.tests.scannersTests;

import org.htmlparser.Node;
import org.htmlparser.PrototypicalNodeFactory;
import org.htmlparser.Tag;
import org.htmlparser.Text;
import org.htmlparser.nodes.AbstractNode;
import org.htmlparser.scanners.CompositeTagScanner;
import org.htmlparser.tags.CompositeTag;
import org.htmlparser.tags.Div;
import org.htmlparser.tags.LinkTag;
import org.htmlparser.tags.TableColumn;
import org.htmlparser.tags.TableRow;
import org.htmlparser.tags.TableTag;
import org.htmlparser.tests.ParserTestCase;
import org.htmlparser.util.ParserException;

public class CompositeTagScannerTest extends ParserTestCase {
    static
    {
        System.setProperty ("org.htmlparser.tests.scannersTests.CompositeTagScannerTest", "CompositeTagScannerTest");
    }

    public CompositeTagScannerTest(String name) {
        super(name);
    }

    private CustomTag parseCustomTag(int expectedNodeCount) throws ParserException {
        parser.setNodeFactory (new PrototypicalNodeFactory (new CustomTag ()));
        parseAndAssertNodeCount(expectedNodeCount);
        assertType("node",CustomTag.class,node[0]);
        CustomTag customTag = (CustomTag)node[0];
        return customTag;
    }

    public void testEmptyCompositeTag() throws ParserException {
        String html = "<Custom/>";
        createParser(html);
        CustomTag customTag = parseCustomTag(1);
        assertEquals("child count",0,customTag.getChildCount());
        assertTrue("custom tag should be xml end tag",customTag.isEmptyXmlTag());
        assertEquals("starting loc",0,customTag.getStartPosition ());
        assertEquals("ending loc",9,customTag.getEndPosition ());
        assertEquals("starting line position",0,customTag.getStartingLineNumber());
        assertEquals("ending line position",0,customTag.getEndingLineNumber());
        assertStringEquals("html",html,customTag.toHtml());
    }

    public void testEmptyCompositeTagAnotherStyle() throws ParserException {
        String html = "<Custom></Custom>";
        createParser(html);
        CustomTag customTag = parseCustomTag(1);
        assertEquals("child count",0,customTag.getChildCount());
        assertFalse("custom tag should not be xml end tag",customTag.isEmptyXmlTag());
        assertEquals("starting loc",0,customTag.getStartPosition ());
        assertEquals("ending loc",8,customTag.getEndPosition ());
        assertEquals("starting line position",0,customTag.getStartingLineNumber());
        assertEquals("ending line position",0,customTag.getEndingLineNumber());
        assertEquals("html",html,customTag.toHtml());
    }

    public void testCompositeTagWithOneTextChild() throws ParserException {
        String html = 
            "<Custom>" +
                "Hello" +
            "</Custom>";
        createParser(html);
        CustomTag customTag = parseCustomTag(1);
        assertEquals("child count",1,customTag.getChildCount());
        assertFalse("custom tag should not be xml end tag",customTag.isEmptyXmlTag());
        assertEquals("starting loc",0,customTag.getStartPosition ());
        assertEquals("ending loc",8,customTag.getEndPosition ());
        assertEquals("starting line position",0,customTag.getStartingLineNumber());
        assertEquals("ending line position",0,customTag.getEndingLineNumber());
        Node child = customTag.childAt(0);
        assertType("child",Text.class,child);
        assertStringEquals("child text","Hello",child.toPlainTextString());
    }

    public void testCompositeTagWithTagChild() throws ParserException {
        String childtag = "<Hello>";
        createParser(
            "<Custom>" +
                childtag +
            "</Custom>"
        );
        CustomTag customTag = parseCustomTag(1);
        assertEquals("child count",1,customTag.getChildCount());
        assertFalse("custom tag should not be xml end tag",customTag.isEmptyXmlTag());
        assertEquals("starting loc",0,customTag.getStartPosition ());
        assertEquals("ending loc",8,customTag.getEndPosition ());
        assertEquals("custom tag starting loc",0,customTag.getStartPosition ());
        assertEquals("custom tag ending loc",24,customTag.getEndTag ().getEndPosition ());

        Node child = customTag.childAt(0);
        assertType("child",Tag.class,child);
        assertStringEquals("child html",childtag,child.toHtml());
    }

    public void testCompositeTagWithAnotherTagChild() throws ParserException {
        String childtag = "<Another/>";
        createParser(
            "<Custom>" +
                 childtag +
            "</Custom>"
        );
        parser.setNodeFactory (
            new PrototypicalNodeFactory (
                new Tag[]
                {
                    new CustomTag (),
                    new AnotherTag (true),
                }));
        parseAndAssertNodeCount(1);
        assertType("node",CustomTag.class,node[0]);
        CustomTag customTag = (CustomTag)node[0];
        assertEquals("child count",1,customTag.getChildCount());
        assertFalse("custom tag should not be xml end tag",customTag.isEmptyXmlTag());
        assertEquals("starting loc",0,customTag.getStartPosition ());
        assertEquals("ending loc",8,customTag.getEndPosition ());
        assertEquals("custom tag starting loc",0,customTag.getStartPosition ());
        assertEquals("custom tag ending loc",27,customTag.getEndTag ().getEndPosition ());

        Node child = customTag.childAt(0);
        assertType("child",AnotherTag.class,child);
        AnotherTag tag = (AnotherTag)child;
        assertEquals("another tag start pos",8,tag.getStartPosition ());
        assertEquals("another tag ending pos",18,tag.getEndPosition ());

        assertEquals("custom end tag start pos",18,customTag.getEndTag().getStartPosition ());
        assertStringEquals("child html",childtag,child.toHtml());
    }

    public void testParseTwoCompositeTags() throws ParserException {
        createParser(
            "<Custom>" +
            "</Custom>" +
            "<Custom/>"
        );
        parser.setNodeFactory (new PrototypicalNodeFactory (new CustomTag ()));
        parseAndAssertNodeCount(2);
        assertType("tag 1",CustomTag.class,node[0]);
        assertType("tag 2",CustomTag.class,node[1]);
    }

    public void testXmlTypeCompositeTags() throws ParserException {
        createParser(
            "<Custom>" +
                "<Another name=\"subtag\"/>" +
                "<Custom />" +
            "</Custom>" +
            "<Custom/>"
        );
        parser.setNodeFactory (
            new PrototypicalNodeFactory (
                new Tag[] {
                    new CustomTag (),
                    new AnotherTag (false),
                }));
        parseAndAssertNodeCount(2);
        assertType("first node",CustomTag.class,node[0]);
        assertType("second node",CustomTag.class,node[1]);
        CustomTag customTag = (CustomTag)node[0];
        Node node = customTag.childAt(0);
        assertType("first child",AnotherTag.class,node);
        node = customTag.childAt(1);
        assertType("second child",CustomTag.class,node);
    }

    public void testCompositeTagWithNestedTag() throws ParserException {
        createParser(
            "<Custom>" +
                "<Another>" +
                    "Hello" +
                "</Another>" +
                "<Custom/>" +
            "</Custom>" +
            "<Custom/>"
        );
        parser.setNodeFactory (
            new PrototypicalNodeFactory (
                new Tag[] {
                    new CustomTag (),
                    new AnotherTag (false),
                }));
        parseAndAssertNodeCount(2);
        assertType("first node",CustomTag.class,node[0]);
        assertType("second node",CustomTag.class,node[1]);
        CustomTag customTag = (CustomTag)node[0];
        Node node = customTag.childAt(0);
        assertType("first child",AnotherTag.class,node);
        AnotherTag anotherTag = (AnotherTag)node;
        assertEquals("another tag children count",1,anotherTag.getChildCount());
        node = anotherTag.childAt(0);
        assertType("nested child",Text.class,node);
        Text text = (Text)node;
        assertEquals("text","Hello",text.toPlainTextString());
    }

    public void testCompositeTagWithTwoNestedTags() throws ParserException {
        createParser(
            "<Custom>" +
                "<Another>" +
                    "Hello" +
                "</Another>" +
                "<unknown>" +
                    "World" +
                "</unknown>" +
                "<Custom/>" +
            "</Custom>" +
            "<Custom/>"
        );
        parser.setNodeFactory (
            new PrototypicalNodeFactory (
                new Tag[] {
                    new CustomTag (),
                    new AnotherTag (false),
                }));
        parseAndAssertNodeCount(2);
        assertType("first node",CustomTag.class,node[0]);
        assertType("second node",CustomTag.class,node[1]);
        CustomTag customTag = (CustomTag)node[0];
        assertEquals("first custom tag children count",5,customTag.getChildCount());
        Node node = customTag.childAt(0);
        assertType("first child",AnotherTag.class,node);
        AnotherTag anotherTag = (AnotherTag)node;
        assertEquals("another tag children count",1,anotherTag.getChildCount());
        node = anotherTag.childAt(0);
        assertType("nested child",Text.class,node);
        Text text = (Text)node;
        assertEquals("text","Hello",text.toPlainTextString());
    }

    public void testErroneousCompositeTag() throws ParserException {
        String html = "<custom>";
        createParser(html);
        CustomTag customTag = parseCustomTag(1);
        assertEquals("child count",0,customTag.getChildCount());
        assertFalse("custom tag should not be xml end tag",customTag.isEmptyXmlTag());
        assertEquals("starting loc",0,customTag.getStartPosition ());
        assertEquals("ending loc",8,customTag.getEndPosition ());
        assertEquals("starting line position",0,customTag.getStartingLineNumber());
        assertEquals("ending line position",0,customTag.getEndingLineNumber());
        assertStringEquals("html",html + "</custom>",customTag.toHtml());
    }

    public void testErroneousCompositeTagWithChildren() throws ParserException {
        String html = "<custom>" + "<firstChild>" + "<secondChild>";
        createParser(html);
        CustomTag customTag = parseCustomTag(1);
        assertEquals("child count",2,customTag.getChildCount());
        assertFalse("custom tag should not be xml end tag",customTag.isEmptyXmlTag());
        assertEquals("starting loc",0,customTag.getStartPosition ());
        assertEquals("ending loc",8,customTag.getEndPosition ());
        assertEquals("starting line position",0,customTag.getStartingLineNumber());
        assertEquals("ending line position",0,customTag.getEndingLineNumber());
        assertStringEquals("html",html + "</custom>",customTag.toHtml());
    }

    public void testErroneousCompositeTagWithChildrenAndLineBreak() throws ParserException {
        String html = "<custom>" + "<firstChild>" + "\n" + "<secondChild>";
        createParser(html);
        CustomTag customTag = parseCustomTag(1);
        assertEquals("child count",3,customTag.getChildCount());
        assertFalse("custom tag should not be xml end tag",customTag.isEmptyXmlTag());
        assertEquals("starting loc",0,customTag.getStartPosition ());
        assertEquals("ending loc",8,customTag.getEndPosition ());
        assertEquals("starting line position",0,customTag.getStartingLineNumber());
        assertEquals("ending line position",1,customTag.getEndTag ().getEndingLineNumber());
        assertStringEquals("html", html + "</custom>", customTag.toHtml()
        );
    }

    public void testTwoConsecutiveErroneousCompositeTags() throws ParserException {
        String tag1 = "<custom>something";
        String tag2 = "<custom></endtag>";
        createParser(tag1 + tag2);
        parser.setNodeFactory (new PrototypicalNodeFactory (new CustomTag (false)));
        parseAndAssertNodeCount(2);
        CustomTag customTag = (CustomTag)node[0];
        assertEquals("child count",1,customTag.getChildCount());
        assertFalse("custom tag should not be xml end tag",customTag.isEmptyXmlTag());
        assertEquals("starting loc",0,customTag.getStartPosition ());
        assertEquals("ending loc",8,customTag.getEndPosition ());
        assertEquals("ending loc of custom tag",17,customTag.getEndTag ().getEndPosition ());
        assertEquals("starting line position",0,customTag.getStartingLineNumber());
        assertEquals("ending line position",0,customTag.getEndTag ().getEndingLineNumber());
        assertStringEquals("1st custom tag", tag1 + "</custom>", customTag.toHtml());
        customTag = (CustomTag)node[1];
        assertStringEquals("2nd custom tag", tag2 + "</custom>", customTag.toHtml());
    }

    public void testCompositeTagWithErroneousAnotherTagAndLineBreak() throws ParserException {
        String another = "<another>";
        String custom = "<custom>\n</custom>";
        createParser(
            another +
            custom
        );
        parser.setNodeFactory (
            new PrototypicalNodeFactory (
                new Tag[] {
                    new CustomTag (),
                    new AnotherTag (false),
                }));
        parseAndAssertNodeCount(2);
        AnotherTag anotherTag = (AnotherTag)node[0];
        assertEquals("another tag child count",0,anotherTag.getChildCount());

        CustomTag customTag = (CustomTag)node[1];
        assertEquals("child count",1,customTag.getChildCount());
        assertFalse("custom tag should not be xml end tag",customTag.isEmptyXmlTag());
        assertEquals("starting loc",9,customTag.getStartPosition ());
        assertEquals("ending loc",17,customTag.getEndPosition ());
        assertEquals("starting line position",0,customTag.getStartingLineNumber());
        assertEquals("ending line position",1,customTag.getEndTag ().getEndingLineNumber());
        assertStringEquals("another tag html",another + "</another>",anotherTag.toHtml());
        assertStringEquals("custom tag html",custom,customTag.toHtml());
    }

    public void testCompositeTagWithErroneousAnotherTag() throws ParserException {
        createParser(
            "<custom>" +
                "<another>" +
            "</custom>"
        );
        parser.setNodeFactory (
            new PrototypicalNodeFactory (
                new Tag[]
                {
                    new CustomTag (),
                    new AnotherTag (true),
                }));
        parseAndAssertNodeCount(1);
        assertType("node",CustomTag.class,node[0]);
        CustomTag customTag = (CustomTag)node[0];
        assertEquals("child count",1,customTag.getChildCount());
        assertFalse("custom tag should be xml end tag",customTag.isEmptyXmlTag());
        assertEquals("starting loc",0,customTag.getStartPosition ());
        assertEquals("ending loc",8,customTag.getEndPosition ());
        AnotherTag anotherTag = (AnotherTag)customTag.childAt(0);
        assertEquals("another tag ending loc",17,anotherTag.getEndPosition ());
        assertEquals("starting line position",0,customTag.getStartingLineNumber());
        assertEquals("ending line position",0,customTag.getEndingLineNumber());
        assertStringEquals("html","<custom><another></another></custom>",customTag.toHtml());
    }

    public void testCompositeTagWithDeadlock() throws ParserException {
        createParser(
            "<custom>" +
                "<another>something" +
            "</custom>"+
            "<custom>" +
                "<another>else</another>" +
            "</custom>"
        );
        parser.setNodeFactory (
            new PrototypicalNodeFactory (
                new Tag[]
                {
                    new CustomTag (),
                    new AnotherTag (true),
                }));
        parseAndAssertNodeCount(2);
        assertType("node",CustomTag.class,node[0]);
        CustomTag customTag = (CustomTag)node[0];

        assertEquals("child count",1,customTag.getChildCount());
        assertFalse("custom tag should not be xml end tag",customTag.isEmptyXmlTag());
        assertEquals("starting loc",0,customTag.getStartPosition ());
        assertEquals("ending loc",8,customTag.getEndPosition ());
        assertEquals("starting line position",0,customTag.getStartingLineNumber());
        assertEquals("ending line position",0,customTag.getEndingLineNumber());
        AnotherTag anotherTag = (AnotherTag)customTag.childAt(0);
        assertEquals("anotherTag child count",1,anotherTag.getChildCount());
        Text stringNode = (Text)anotherTag.childAt(0);
        assertStringEquals("anotherTag child text","something",stringNode.toPlainTextString());
        assertStringEquals(
            "first custom tag html",
            "<custom><another>something</another></custom>",
            customTag.toHtml()
        );
        customTag = (CustomTag)node[1];
        assertStringEquals(
            "second custom tag html",
            "<custom><another>else</another></custom>",
            customTag.toHtml()
        );
    }

    public void testCompositeTagCorrectionWithSplitLines() throws ParserException {
        createParser(
            "<custom>" +
                "<another><abcdefg>\n" +
            "</custom>"
        );
        parser.setNodeFactory (
            new PrototypicalNodeFactory (
                new Tag[]
                {
                    new CustomTag (),
                    new AnotherTag (true),
                }));
        parseAndAssertNodeCount(1);
        assertType("node",CustomTag.class,node[0]);
        CustomTag customTag = (CustomTag)node[0];
        assertEquals("child count",1,customTag.getChildCount());
        assertFalse("custom tag should not be xml end tag",customTag.isEmptyXmlTag());
        assertEquals("starting loc",0,customTag.getStartPosition ());
        assertEquals("ending loc",8,customTag.getEndPosition ());
        AnotherTag anotherTag = (AnotherTag)customTag.childAt(0);
        assertEquals("anotherTag child count",2,anotherTag.getChildCount());
        assertEquals("anotherTag end loc",27,anotherTag.getEndTag ().getEndPosition ());
        assertEquals("custom end tag begin loc",27,customTag.getEndTag().getStartPosition ());
        assertEquals("custom end tag end loc",36,customTag.getEndTag().getEndPosition ());
    }

    public void testCompositeTagWithSelfChildren() throws ParserException
    {
        String tag1 = "<custom>";
        String tag2 = "<custom>something</custom>";
        String tag3 = "</custom>";
        createParser(tag1 + tag2 + tag3);
        parser.setNodeFactory (
            new PrototypicalNodeFactory (
                new Tag[] {
                    new CustomTag (false),
                    new AnotherTag (false),
                }));
        parseAndAssertNodeCount(3);

        CustomTag customTag = (CustomTag)node[0];
        assertEquals("child count",0,customTag.getChildCount());
        assertFalse("custom tag should not be xml end tag",customTag.isEmptyXmlTag());

        assertStringEquals(
            "first custom tag html",
            tag1 + "</custom>",
            customTag.toHtml()
        );
        customTag = (CustomTag)node[1];
        assertStringEquals(
            "second custom tag html",
            tag2,
            customTag.toHtml()
        );
        Tag endTag = (Tag)node[2];
        assertStringEquals(
            "third custom tag html",
            tag3,
            endTag.toHtml()
        );
    }

    public void testParentConnections() throws ParserException {
        String tag1 = "<custom>";
        String tag2 = "<custom>something</custom>";
        String tag3 = "</custom>";
        createParser(tag1 + tag2 + tag3);
        parser.setNodeFactory (
            new PrototypicalNodeFactory (
                new Tag[] {
                    new CustomTag (false),
                    new AnotherTag (false),
                }));
        parseAndAssertNodeCount(3);

        CustomTag customTag = (CustomTag)node[0];

        assertStringEquals(
            "first custom tag html",
            tag1 + "</custom>",
            customTag.toHtml()
        );
        assertNull(
            "first custom tag should have no parent",
            customTag.getParent()
        );

        customTag = (CustomTag)node[1];
        assertStringEquals(
            "second custom tag html",
            tag2,
            customTag.toHtml()
        );
        assertNull(
            "second custom tag should have no parent",
            customTag.getParent()
        );

        Node firstChild = customTag.childAt(0);
        assertType("firstChild",Text.class,firstChild);
        Node parent = firstChild.getParent();
        assertNotNull("first child parent should not be null",parent);
        assertSame("parent and custom tag should be the same",customTag,parent);

        Tag endTag = (Tag)node[2];
        assertStringEquals(
            "third custom tag html",
            tag3,
            endTag.toHtml()
        );
        assertNull(
            "end tag should have no parent",
            endTag.getParent()
        );

    }

    public void testUrlBeingProvidedToCreateTag() throws ParserException {
        createParser("<Custom/>","http://www.yahoo.com");

        parser.setNodeFactory (new PrototypicalNodeFactory (new CustomTag ()));
        parseAndAssertNodeCount(1);
        assertStringEquals("url","http://www.yahoo.com",((AbstractNode)node[0]).getPage ().getUrl ());
    }

    public void testComplexNesting() throws ParserException {
        createParser(
            "<custom>" +
                "<custom>" +
                    "<another>" +
                "</custom>" +
                "<custom>" +
                    "<another>" +
                "</custom>" +
            "</custom>"
        );
        parser.setNodeFactory (
            new PrototypicalNodeFactory (
                new Tag[] {
                    new CustomTag (),
                    new AnotherTag (false),
                }));
        parseAndAssertNodeCount(1);
        assertType("root node",CustomTag.class, node[0]);
        CustomTag root = (CustomTag)node[0];
        assertNodeCount("child count",2,root.getChildrenAsNodeArray());
        Node child = root.childAt(0);
        assertType("child",CustomTag.class,child);
        CustomTag customChild = (CustomTag)child;
        assertNodeCount("grand child count",1,customChild.getChildrenAsNodeArray());
        Node grandchild = customChild.childAt(0);
        assertType("grandchild",AnotherTag.class,grandchild);
    }

    public void testDisallowedChildren() throws ParserException {
        createParser(
            "<custom>\n" +
            "Hello" +
            "<custom>\n" +
            "World" +
            "<custom>\n" +
            "Hey\n" +
            "</custom>"
        );
        parser.setNodeFactory (new PrototypicalNodeFactory (new CustomTag (false)));
        parseAndAssertNodeCount(3);
        for (int i=0;i<nodeCount;i++) {
            assertType("node "+i,CustomTag.class,node[i]);
        }
    }

    public static class CustomScanner extends CompositeTagScanner {
        private static final String MATCH_NAME [] = { "CUSTOM" };
        public CustomScanner() {
        }

        public String[] getID() {
            return MATCH_NAME;
        }
    }

    public static class AnotherScanner extends CompositeTagScanner {
        private static final String MATCH_NAME [] = { "ANOTHER" };
        public AnotherScanner() {
        }

        public String[] getID() {
            return MATCH_NAME;
        }

        protected boolean isBrokenTag() {
            return false;
        }

    }

    public static class CustomTag extends CompositeTag
    {
        /**
         * The set of names handled by this tag.
         */
        private static final String[] mIds = new String[] {"CUSTOM"};

        protected String[] mEnders;

        /**
         * The default scanner for custom tags.
         */
        protected final static CustomScanner mCustomScanner = new CustomScanner ();

        public CustomTag ()
        {
            this (true);
        }

        public CustomTag (boolean selfChildrenAllowed)
        {
            if (selfChildrenAllowed)
                mEnders = new String[0];
            else
                mEnders = mIds;
            setThisScanner (mCustomScanner);
        }

        /**
         * Return the set of names handled by this tag.
         * @return The names to be matched that create tags of this type.
         */
        public String[] getIds ()
        {
            return (mIds);
        }

        /**
         * Return the set of tag names that cause this tag to finish.
         * @return The names of following tags that stop further scanning.
         */
        public String[] getEnders ()
        {
            return (mEnders);
        }

        
    }

    public static class AnotherTag extends CompositeTag
    {
        /**
         * The set of names handled by this tag.
         */
        private static final String[] mIds = new String[] {"ANOTHER"};

        /**
         * The set of tag names that indicate the end of this tag.
         */
        private final String[] mEnders;

        /**
         * The set of end tag names that indicate the end of this tag.
         */
        private final String[] mEndTagEnders;

        /**
         * The default scanner for custom tags.
         */
        protected final static AnotherScanner mAnotherScanner = new AnotherScanner ();

        public AnotherTag (boolean acceptCustomTagsButDontAcceptCustomEndTags)
        {
            if (acceptCustomTagsButDontAcceptCustomEndTags)
            {
                mEnders = new String[0];
                mEndTagEnders = new String[] {"CUSTOM"};
            }
            else
            {
                mEnders = new String[] {"CUSTOM"};
                mEndTagEnders = new String[] {"CUSTOM"};
            }
            setThisScanner (mAnotherScanner);
        }

        /**
         * Return the set of names handled by this tag.
         * @return The names to be matched that create tags of this type.
         */
        public String[] getIds ()
        {
            return (mIds);
        }

        /**
         * Return the set of tag names that cause this tag to finish.
         * @return The names of following tags that stop further scanning.
         */
        public String[] getEnders ()
        {
            return (mEnders);
        }

        /**
         * Return the set of end tag names that cause this tag to finish.
         * @return The names of following end tags that stop further scanning.
         */
        public String[] getEndTagEnders ()
        {
            return (mEndTagEnders);
        }
    }

    /**
     * Extracted from "http://scores.nba.com/games/20031029/scoreboard.html"
     * which has a lot of table columns with unclosed DIV tags because the
     * closing DIV doesn't have a slash.
     * This caused java.lang.StackOverflowError on Windows.
     * Tests the new non-recursive CompositeTagScanner with the walk back
     * through the parse stack.
     * See also Bug #750117 StackOverFlow while Node-Iteration and
     * others.
     */
    public void testInvalidNesting () throws ParserException
    {
        String html = "<table cellspacing=\"2\" cellpadding=\"0\" border=\"0\" width=\"600\">\n"
            + "<tr>\n"
            + "<td><div class=\"ScoreBoardSec\">&nbsp;<a  target=\"_parent\" class=\"ScoreBoardSec\" href=\"http://www.nba.com/heat/\">Heat</a><div></td>\n"
            + "</tr>\n"
            + "</table>";
        createParser (html);
        parseAndAssertNodeCount (1);
        assertType ("table", TableTag.class, node[0]);
        TableTag table = (TableTag)node[0];
        assertTrue ("table should have 3 nodes", 3 == table.getChildCount ());
        assertType ("row", TableRow.class, table.childAt (1));
        TableRow row = (TableRow)table.childAt (1);
        assertTrue ("row should have 3 nodes", 3 == row.getChildCount ());
        assertType ("column", TableColumn.class, row.childAt (1));
        TableColumn column = (TableColumn)row.childAt (1);
        assertTrue ("column should have 1 node", 1 == column.getChildCount ());
        assertType ("element", Div.class, column.childAt (0));
        Div div = (Div)column.childAt (0);
        assertTrue ("div should have 3 nodes", 3 == div.getChildCount ());
        assertType ("link", LinkTag.class, div.childAt (1));
        LinkTag link = (LinkTag)div.childAt (1);
        assertTrue ("link contents", link.getLink ().equals ("http://www.nba.com/heat/"));
        assertType ("bogus div", Div.class, div.childAt (2));
        assertTrue ("bogus div should have no children", 0 == ((Div)div.childAt (2)).getChildCount ());
    }
}
