/* Copyright (c) 2002,2003,2004 Stefan Haustein, Oberhausen, Rhld., Germany
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The  above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE. */

// Contributors: Bjorn Aadland, Chris Bartley, Nicola Fankhauser,
//               Victor Havin,  Christian Kurzke, Bogdan Onoiu,
//                Elias Ross, Jain Sanjay, David Santoro.

package org.kxml2.wap;

import java.io.*;
import java.util.Vector;
import java.util.Hashtable;

import org.xmlpull.v1.*;


public class WbxmlParser implements XmlPullParser {

	static final String HEX_DIGITS = "0123456789abcdef";
	
	/** Parser event type for Wbxml-specific events. The Wbxml event code can be 
	 * accessed with getWapCode() */
	
    public static final int WAP_EXTENSION = 64;
    
    static final private String UNEXPECTED_EOF =
    "Unexpected EOF";
    static final private String ILLEGAL_TYPE =
    "Wrong event type";
    
    private InputStream in;
    
    private int TAG_TABLE = 0;
    private int ATTR_START_TABLE = 1;
    private int ATTR_VALUE_TABLE = 2;
    
    private String[] attrStartTable;
    private String[] attrValueTable;
    private String[] tagTable;
    private byte[] stringTable;
    private Hashtable cacheStringTable = null;
    private boolean processNsp;
    
    private int depth;
    private String[] elementStack = new String[16];
    private String[] nspStack = new String[8];
    private int[] nspCounts = new int[4];
    
    private int attributeCount;
    private String[] attributes = new String[16];
    private int nextId = -2;
    
    private Vector tables = new Vector();
    
    private int version;
    private int publicIdentifierId;
    
    //    StartTag current;
    //    ParseEvent next;
    
    private String prefix;
    private String namespace;
    private String name;
    private String text;

    private Object wapExtensionData;
    private int wapCode;
    
    private int type;
    
    private boolean degenerated;
    private boolean isWhitespace;
    private String encoding;
    
    public boolean getFeature(String feature) {
        if (XmlPullParser
        .FEATURE_PROCESS_NAMESPACES
        .equals(feature))
            return processNsp;
        else
            return false;
    }
    
    public String getInputEncoding() {
        return encoding;
    }
    
    public void defineEntityReplacementText(
    String entity,
    String value)
    throws XmlPullParserException {
        
        // just ignore, has no effect
    }
    
    public Object getProperty(String property) {
        return null;
    }
    
    public int getNamespaceCount(int depth) {
        if (depth > this.depth)
            throw new IndexOutOfBoundsException();
        return nspCounts[depth];
    }
    
    public String getNamespacePrefix(int pos) {
        return nspStack[pos << 1];
    }
    
    public String getNamespaceUri(int pos) {
        return nspStack[(pos << 1) + 1];
    }
    
    public String getNamespace(String prefix) {
        
        if ("xml".equals(prefix))
            return "http://www.w3.org/XML/1998/namespace";
        if ("xmlns".equals(prefix))
            return "http://www.w3.org/2000/xmlns/";
        
        for (int i = (getNamespaceCount(depth) << 1) - 2;
        i >= 0;
        i -= 2) {
            if (prefix == null) {
                if (nspStack[i] == null)
                    return nspStack[i + 1];
            }
            else if (prefix.equals(nspStack[i]))
                return nspStack[i + 1];
        }
        return null;
    }
    
    public int getDepth() {
        return depth;
    }
    
    public String getPositionDescription() {
        
        StringBuffer buf =
        new StringBuffer(
        type < TYPES.length ? TYPES[type] : "unknown");
        buf.append(' ');
        
        if (type == START_TAG || type == END_TAG) {
            if (degenerated)
                buf.append("(empty) ");
            buf.append('<');
            if (type == END_TAG)
                buf.append('/');
            
            if (prefix != null)
                buf.append("{" + namespace + "}" + prefix + ":");
            buf.append(name);
            
            int cnt = attributeCount << 2;
            for (int i = 0; i < cnt; i += 4) {
                buf.append(' ');
                if (attributes[i + 1] != null)
                    buf.append(
                    "{"
                    + attributes[i]
                    + "}"
                    + attributes[i
                    + 1]
                    + ":");
                buf.append(
                attributes[i
                + 2]
                + "='"
                + attributes[i
                + 3]
                + "'");
            }
            
            buf.append('>');
        }
        else if (type == IGNORABLE_WHITESPACE);
        else if (type != TEXT)
            buf.append(getText());
        else if (isWhitespace)
            buf.append("(whitespace)");
        else {
            String text = getText();
            if (text.length() > 16)
                text = text.substring(0, 16) + "...";
            buf.append(text);
        }
        
        return buf.toString();
    }
    
    public int getLineNumber() {
        return -1;
    }
    
    public int getColumnNumber() {
        return -1;
    }
    
    public boolean isWhitespace()
    throws XmlPullParserException {
        if (type != TEXT
        && type != IGNORABLE_WHITESPACE
        && type != CDSECT)
            exception(ILLEGAL_TYPE);
        return isWhitespace;
    }
    
    public String getText() {
        return text;
    }
    
    public char[] getTextCharacters(int[] poslen) {
        if (type >= TEXT) {
            poslen[0] = 0;
            poslen[1] = text.length();
            char[] buf = new char[text.length()];
            text.getChars(0, text.length(), buf, 0);
            return buf;
        }
        
        poslen[0] = -1;
        poslen[1] = -1;
        return null;
    }
    
    public String getNamespace() {
        return namespace;
    }
    
    public String getName() {
        return name;
    }
    
    public String getPrefix() {
        return prefix;
    }
    
    public boolean isEmptyElementTag()
    throws XmlPullParserException {
        if (type != START_TAG)
            exception(ILLEGAL_TYPE);
        return degenerated;
    }
    
    public int getAttributeCount() {
        return attributeCount;
    }
    
    public String getAttributeType(int index) {
        return "CDATA";
    }
    
    public boolean isAttributeDefault(int index) {
        return false;
    }
    
    public String getAttributeNamespace(int index) {
        if (index >= attributeCount)
            throw new IndexOutOfBoundsException();
        return attributes[index << 2];
    }
    
    public String getAttributeName(int index) {
        if (index >= attributeCount)
            throw new IndexOutOfBoundsException();
        return attributes[(index << 2) + 2];
    }
    
    public String getAttributePrefix(int index) {
        if (index >= attributeCount)
            throw new IndexOutOfBoundsException();
        return attributes[(index << 2) + 1];
    }
    
    public String getAttributeValue(int index) {
        if (index >= attributeCount)
            throw new IndexOutOfBoundsException();
        return attributes[(index << 2) + 3];
    }
    
    public String getAttributeValue(
    String namespace,
    String name) {
        
        for (int i = (attributeCount << 2) - 4;
        i >= 0;
        i -= 4) {
            if (attributes[i + 2].equals(name)
            && (namespace == null
            || attributes[i].equals(namespace)))
                return attributes[i + 3];
        }
        
        return null;
    }
    
    public int getEventType() throws XmlPullParserException {
        return type;
    }
    
    
    // TODO: Reuse resolveWapExtension here? Raw Wap extensions would still be accessible
    // via nextToken();  ....?
    
    public int next() throws XmlPullParserException, IOException {
        
        isWhitespace = true;
        int minType = 9999;
        
        while (true) {
            
            String save = text;
            
            nextImpl();
            
            if (type < minType)
                minType = type;
            
            if (minType > CDSECT) continue; // no "real" event so far
            
            if (minType >= TEXT) {  // text, see if accumulate
                
                if (save != null) text = text == null ? save : save + text;
                
                switch(peekId()) {
                    case Wbxml.ENTITY:
                    case Wbxml.STR_I:
                    case Wbxml.STR_T:
                    case Wbxml.LITERAL:
                    case Wbxml.LITERAL_C:
                    case Wbxml.LITERAL_A:
                    case Wbxml.LITERAL_AC: continue;
                }
            }
            
            break;
        }
        
        type = minType;
        
        if (type > TEXT)
            type = TEXT;
        
        return type;
    }
    
    
    public int nextToken() throws XmlPullParserException, IOException {
        
        isWhitespace = true;
        nextImpl();
        return type;
    }
    
    
    
    public int nextTag() throws XmlPullParserException, IOException {
        
        next();
        if (type == TEXT && isWhitespace)
            next();
        
        if (type != END_TAG && type != START_TAG)
            exception("unexpected type");
        
        return type;
    }
    
    
    public String nextText() throws XmlPullParserException, IOException {
        if (type != START_TAG)
            exception("precondition: START_TAG");
        
        next();
        
        String result;
        
        if (type == TEXT) {
            result = getText();
            next();
        }
        else
            result = "";
        
        if (type != END_TAG)
            exception("END_TAG expected");
        
        return result;
    }
    
    
    public void require(int type, String namespace, String name)
    throws XmlPullParserException, IOException {
        
        if (type != this.type
        || (namespace != null && !namespace.equals(getNamespace()))
        || (name != null && !name.equals(getName())))
            exception(
            "expected: " + (type == WAP_EXTENSION ? "WAP Ext." : (TYPES[type] + " {" + namespace + "}" + name)));
    }
    
    
    public void setInput(Reader reader) throws XmlPullParserException {
        exception("InputStream required");
    }
    
    public void setInput(InputStream in, String enc)
    throws XmlPullParserException {
        
        this.in = in;
        
        try {
            version = readByte();
            publicIdentifierId = readInt();
            
            if (publicIdentifierId == 0)
                readInt();
            
            int charset = readInt(); // skip charset
            
            if (null == enc){
                switch (charset){
                    case   4: encoding = "ISO-8859-1"; break;
                    case 106: encoding = "UTF-8";      break;
                    // add more if you need them
                    // http://www.iana.org/assignments/character-sets
                    // case MIBenum: encoding = Name  break;
                    default:  throw new UnsupportedEncodingException(""+charset);
                } 
            }else{
                encoding = enc;
            }

            int strTabSize = readInt();
            stringTable = new byte[strTabSize];
            
            int ok = 0;
            while(ok < strTabSize){
            	int cnt = in.read(stringTable, ok, strTabSize - ok);
            	if(cnt <= 0) break;
            	ok += cnt;
            }
            
            selectPage(0, true);
            selectPage(0, false);
        }
        catch (IOException e) {
            exception("Illegal input format");
        }
    }
    
    public void setFeature(String feature, boolean value)
    throws XmlPullParserException {
        if (XmlPullParser.FEATURE_PROCESS_NAMESPACES.equals(feature))
            processNsp = value;
        else
            exception("unsupported feature: " + feature);
    }
    
    public void setProperty(String property, Object value)
    throws XmlPullParserException {
        throw new XmlPullParserException("unsupported property: " + property);
    }
    
    // ---------------------- private / internal methods
    
    private final boolean adjustNsp()
    throws XmlPullParserException {
        
        boolean any = false;
        
        for (int i = 0; i < attributeCount << 2; i += 4) {
            // * 4 - 4; i >= 0; i -= 4) {
            
            String attrName = attributes[i + 2];
            int cut = attrName.indexOf(':');
            String prefix;
            
            if (cut != -1) {
                prefix = attrName.substring(0, cut);
                attrName = attrName.substring(cut + 1);
            }
            else if (attrName.equals("xmlns")) {
                prefix = attrName;
                attrName = null;
            }
            else
                continue;
            
            if (!prefix.equals("xmlns")) {
                any = true;
            }
            else {
                int j = (nspCounts[depth]++) << 1;
                
                nspStack = ensureCapacity(nspStack, j + 2);
                nspStack[j] = attrName;
                nspStack[j + 1] = attributes[i + 3];
                
                if (attrName != null
                && attributes[i + 3].equals(""))
                    exception("illegal empty namespace");
                
                //  prefixMap = new PrefixMap (prefixMap, attrName, attr.getValue ());
                
                //System.out.println (prefixMap);
                
                System.arraycopy(
                attributes,
                i + 4,
                attributes,
                i,
                ((--attributeCount) << 2) - i);
                
                i -= 4;
            }
        }
        
        if (any) {
            for (int i = (attributeCount << 2) - 4;
            i >= 0;
            i -= 4) {
                
                String attrName = attributes[i + 2];
                int cut = attrName.indexOf(':');
                
                if (cut == 0)
                    throw new RuntimeException(
                    "illegal attribute name: "
                    + attrName
                    + " at "
                    + this);
                
                else if (cut != -1) {
                    String attrPrefix =
                    attrName.substring(0, cut);
                    
                    attrName = attrName.substring(cut + 1);
                    
                    String attrNs = getNamespace(attrPrefix);
                    
                    if (attrNs == null)
                        throw new RuntimeException(
                        "Undefined Prefix: "
                        + attrPrefix
                        + " in "
                        + this);
                    
                    attributes[i] = attrNs;
                    attributes[i + 1] = attrPrefix;
                    attributes[i + 2] = attrName;
                    
                    for (int j = (attributeCount << 2) - 4;
                    j > i;
                    j -= 4)
                        if (attrName.equals(attributes[j + 2])
                        && attrNs.equals(attributes[j]))
                            exception(
                            "Duplicate Attribute: {"
                            + attrNs
                            + "}"
                            + attrName);
                }
            }
        }
        
        int cut = name.indexOf(':');
        
        if (cut == 0)
            exception("illegal tag name: " + name);
        else if (cut != -1) {
            prefix = name.substring(0, cut);
            name = name.substring(cut + 1);
        }
        
        this.namespace = getNamespace(prefix);
        
        if (this.namespace == null) {
            if (prefix != null)
                exception("undefined prefix: " + prefix);
            this.namespace = NO_NAMESPACE;
        }
        
        return any;
    }
    
    private final void setTable(int page, int type, String[] table) {
        if(stringTable != null){
            throw new RuntimeException("setXxxTable must be called before setInput!");
        }
        while(tables.size() < 3*page +3){
            tables.addElement(null);
        }
        tables.setElementAt(table, page*3+type);
    }
    
    
    
    
    
    private final void exception(String desc)
    throws XmlPullParserException {
        throw new XmlPullParserException(desc, this, null);
    }
    
    
    private void selectPage(int nr, boolean tags) throws XmlPullParserException{
        if(tables.size() == 0 && nr == 0) return;
        
        if(nr*3 > tables.size())
            exception("Code Page "+nr+" undefined!");
        
        if(tags)
            tagTable = (String[]) tables.elementAt(nr * 3 + TAG_TABLE);
        else {
            attrStartTable = (String[]) tables.elementAt(nr * 3 + ATTR_START_TABLE);
            attrValueTable = (String[]) tables.elementAt(nr * 3 + ATTR_VALUE_TABLE);
        }
    }
    
    private final void nextImpl()
    throws IOException, XmlPullParserException {
        
        String s;
        
        if (type == END_TAG) {
            depth--;
        }
        
        if (degenerated) {
            type = XmlPullParser.END_TAG;
            degenerated = false;
            return;
        }
        
        text = null;
        prefix = null;
        name = null;
        
        int id = peekId ();
        while(id == Wbxml.SWITCH_PAGE){
            nextId = -2;
            selectPage(readByte(), true);
            id = peekId();
        }
        nextId = -2;
        
        switch (id) {
            case -1 :
                type = XmlPullParser.END_DOCUMENT;
                break;
                
            case Wbxml.END : 
            {
                int sp = (depth - 1) << 2;
                
                type = END_TAG;
                namespace = elementStack[sp];
                prefix = elementStack[sp + 1];
                name = elementStack[sp + 2];
            }
            break;
            
            case Wbxml.ENTITY : 
            {
                type = ENTITY_REF;
                char c = (char) readInt();
                text = "" + c;
                name = "#" + ((int) c);
            }
            
            break;
            
            case Wbxml.STR_I :
                type = TEXT;
                text = readStrI();
                break;
                
            case Wbxml.EXT_I_0 :
            case Wbxml.EXT_I_1 :
            case Wbxml.EXT_I_2 :
            case Wbxml.EXT_T_0 :
            case Wbxml.EXT_T_1 :
            case Wbxml.EXT_T_2 :
            case Wbxml.EXT_0 :
            case Wbxml.EXT_1 :
            case Wbxml.EXT_2 :
            case Wbxml.OPAQUE :
            	
                type = WAP_EXTENSION;
                wapCode = id;
                wapExtensionData = parseWapExtension(id);
                break;
                
            case Wbxml.PI :
                throw new RuntimeException("PI curr. not supp.");
                // readPI;
                // break;
                
            case Wbxml.STR_T : 
            {
                type = TEXT;
                text = readStrT();
            }
            break;
            
            default :
                parseElement(id);
        }
        //        }
        //      while (next == null);
        
        //        return next;
    }
    
    /** Overwrite this method to intercept all wap events */
    
    public Object parseWapExtension(int id)  throws IOException, XmlPullParserException {
        
        switch (id) {
            case Wbxml.EXT_I_0 :
            case Wbxml.EXT_I_1 :
            case Wbxml.EXT_I_2 :
                return readStrI();
                
            case Wbxml.EXT_T_0 :
            case Wbxml.EXT_T_1 :
            case Wbxml.EXT_T_2 :
                return new Integer(readInt());
                
            case Wbxml.EXT_0 :
            case Wbxml.EXT_1 :
            case Wbxml.EXT_2 :
            	return null;
                
            case Wbxml.OPAQUE : 
            {
                int count = readInt();
                byte[] buf = new byte[count];
                
                while(count > 0){
                	count -= in.read(buf, buf.length-count, count);
                }
                
                return buf;
            } // case OPAQUE
    
            
            default:
                exception("illegal id: "+id);
            	return null; // dead code
        } // SWITCH
    }
    
    public void readAttr() throws IOException, XmlPullParserException {
        
        int id = readByte();
        int i = 0;
        
        while (id != 1) {
            
            while(id == Wbxml.SWITCH_PAGE){
                selectPage(readByte(), false);
                id = readByte();
            }
            
            String name = resolveId(attrStartTable, id);
            StringBuffer value;
            
            int cut = name.indexOf('=');
            
            if (cut == -1)
                value = new StringBuffer();
            else {
                value =
                new StringBuffer(name.substring(cut + 1));
                name = name.substring(0, cut);
            }
            
            id = readByte();
            while (id > 128
            || id == Wbxml.SWITCH_PAGE
            || id == Wbxml.ENTITY
            || id == Wbxml.STR_I
            || id == Wbxml.STR_T
            || (id >= Wbxml.EXT_I_0 && id <= Wbxml.EXT_I_2)
            || (id >= Wbxml.EXT_T_0 && id <= Wbxml.EXT_T_2)) {
                
                switch (id) {
                    case Wbxml.SWITCH_PAGE :
                        selectPage(readByte(), false);
                        break;
                        
                    case Wbxml.ENTITY :
                        value.append((char) readInt());
                        break;
                        
                    case Wbxml.STR_I :
                        value.append(readStrI());
                        break;
                        
                    case Wbxml.EXT_I_0 :
                    case Wbxml.EXT_I_1 :
                    case Wbxml.EXT_I_2 :
                    case Wbxml.EXT_T_0 :
                    case Wbxml.EXT_T_1 :
                    case Wbxml.EXT_T_2 :
                    case Wbxml.EXT_0 :
                    case Wbxml.EXT_1 :
                    case Wbxml.EXT_2 :
                    case Wbxml.OPAQUE :
                        value.append(resolveWapExtension(id, parseWapExtension(id)));
                        break;
                        
                    case Wbxml.STR_T :
                        value.append(readStrT());
                        break;
                        
                    default :
                        value.append(
                        resolveId(attrValueTable, id));
                }
                
                id = readByte();
            }
            
            attributes = ensureCapacity(attributes, i + 4);
            
            attributes[i++] = "";
            attributes[i++] = null;
            attributes[i++] = name;
            attributes[i++] = value.toString();
            
            attributeCount++;
        }
    }
    
    private int peekId () throws IOException {
        if (nextId == -2) {
            nextId = in.read ();
        }
        return nextId;
    }
    
    /** overwrite for own WAP extension handling in attributes and high level parsing 
     * (above nextToken() level) */
    
    protected String resolveWapExtension(int id, Object data){
    	
    	if(data instanceof byte[]){
    		StringBuffer sb = new StringBuffer();
    		byte[] b = (byte[]) data;
    		
    		for (int i = 0; i < b.length; i++) {
    			sb.append(HEX_DIGITS.charAt((b[i] >> 4) & 0x0f));
    			sb.append(HEX_DIGITS.charAt(b[i] & 0x0f));
    		}
    		return sb.toString();
    	}

    	return "$("+data+")";
    }
    
    String resolveId(String[] tab, int id) throws IOException {
        int idx = (id & 0x07f) - 5;
        if (idx == -1){
        	wapCode = -1;
            return readStrT();
        }
        if (idx < 0
        || tab == null
        || idx >= tab.length
        || tab[idx] == null)
            throw new IOException("id " + id + " undef.");
        
        wapCode = idx+5;
        
        return tab[idx];
    }
    
    void parseElement(int id)
    throws IOException, XmlPullParserException {
        
        type = START_TAG;
        name = resolveId(tagTable, id & 0x03f);
        
        attributeCount = 0;
        if ((id & 128) != 0) {
            readAttr();
        }
        
        degenerated = (id & 64) == 0;
        
        int sp = depth++ << 2;
        
        // transfer to element stack
        
        elementStack = ensureCapacity(elementStack, sp + 4);
        elementStack[sp + 3] = name;
        
        if (depth >= nspCounts.length) {
            int[] bigger = new int[depth + 4];
            System.arraycopy(nspCounts, 0, bigger, 0, nspCounts.length);
            nspCounts = bigger;
        }
        
        nspCounts[depth] = nspCounts[depth - 1];
        
        for (int i = attributeCount - 1; i > 0; i--) {
            for (int j = 0; j < i; j++) {
                if (getAttributeName(i)
                .equals(getAttributeName(j)))
                    exception(
                    "Duplicate Attribute: "
                    + getAttributeName(i));
            }
        }
        
        if (processNsp)
            adjustNsp();
        else
            namespace = "";
        
        elementStack[sp] = namespace;
        elementStack[sp + 1] = prefix;
        elementStack[sp + 2] = name;
        
    }
    
    private final String[] ensureCapacity(
    String[] arr,
    int required) {
        if (arr.length >= required)
            return arr;
        String[] bigger = new String[required + 16];
        System.arraycopy(arr, 0, bigger, 0, arr.length);
        return bigger;
    }
    
    int readByte() throws IOException {
        int i = in.read();
        if (i == -1)
            throw new IOException("Unexpected EOF");
        return i;
    }
    
    int readInt() throws IOException {
        int result = 0;
        int i;
        
        do {
            i = readByte();
            result = (result << 7) | (i & 0x7f);
        }
        while ((i & 0x80) != 0);
        
        return result;
    }
    
    String readStrI() throws IOException {
        ByteArrayOutputStream buf = new ByteArrayOutputStream();
        boolean wsp = true;
        while (true){
            int i = in.read();
            if (i == 0){
            	break;
            }
        	if (i == -1){
                throw new IOException(UNEXPECTED_EOF);
        	}
            if (i > 32){
                wsp = false;
            }
            buf.write(i);
        }
        isWhitespace = wsp;
        String result = new String(buf.toByteArray(), encoding);
        buf.close();
        return result;
    }
    
    String readStrT() throws IOException {
        int pos = readInt();
        // As the main reason of stringTable is compression we build a cache of Strings
        // stringTable is supposed to help create Strings from parts which means some cache hit rate
        // This will help to minimize the Strings created when invoking readStrT() repeatedly
        if (cacheStringTable == null){
            //Lazy init if device is not using StringTable but inline 0x03 strings
            cacheStringTable = new Hashtable();
        }
        String forReturn = (String) cacheStringTable.get(new Integer(pos));
        if (forReturn == null){

            int end = pos;
            while(end < stringTable.length && stringTable[end] != '\0'){
            	end++;
			}
            forReturn = new String(stringTable, pos, end-pos, encoding);
            cacheStringTable.put(new Integer(pos), forReturn);
        }
        return forReturn;
    }
    
    /**
     * Sets the tag table for a given page.
     * The first string in the array defines tag 5, the second tag 6 etc.
     */
    
    public void setTagTable(int page, String[] table) {
        setTable(page, TAG_TABLE, table);
        
        //        this.tagTable = tagTable;
        //      if (page != 0)
        //        throw new RuntimeException("code pages curr. not supp.");
    }
    
    /** Sets the attribute start Table for a given page.
     *	The first string in the array defines attribute
     *  5, the second attribute 6 etc. Please use the
     *  character '=' (without quote!) as delimiter
     *  between the attribute name and the (start of the) value
     */
    
    public void setAttrStartTable(
    int page,
    String[] table) {
        
        setTable(page, ATTR_START_TABLE, table);
    }
    
    /** Sets the attribute value Table for a given page.
     *	The first string in the array defines attribute value 0x85,
     *  the second attribute value 0x86 etc.
     */
    
    public void setAttrValueTable(
    int page,
    String[] table) {
        
        setTable(page, ATTR_VALUE_TABLE, table);
    }
    
    /** Returns the token ID for start tags or the event type for wap proprietary events
     * such as OPAQUE.
     */
    
    public int getWapCode(){
    	return wapCode;
    }
    
    public Object getWapExtensionData(){
    	return wapExtensionData;
    }
    
    
}