/* kXML 2 * * Copyright (C) 2000, 2001, 2002 * Stefan Haustein * D-46045 Oberhausen (Rhld.), * Germany. All Rights Reserved. * * The contents of this file are subject to the "Common Public * License" (CPL); you may not use this file except in compliance * with the License. * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific terms governing rights and limitations * under the License. * * Thanks to Paul Palaszewski, Wilhelm Fitzpatrick, * Eric Foster-Johnson, Michael Angel, and Liam Quinn for providing various * fixes and hints for the KXML 1 parser. * */ // Contributors: Bjorn Aadland package org.kxml2.wap; import java.io.*; import org.xmlpull.v1.*; /** * PLEASE NOTE: Does not even compile yet * * Still Todo: <ul> <li>implement Processing Instructions</li> <li>implement support for more than one codepages</li> </ul> */ public class WbxmlParser implements XmlPullParser { 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 String[] attrStartTable; private String[] attrValueTable; private String[] tagTable; private String stringTable; 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; int version; int publicIdentifierId; int charSet; // StartTag current; // ParseEvent next; private String prefix; private String namespace; private String name; private String text; // private String encoding; private Object wapExtensionData; private int wapExtensionCode; private int type; private boolean degenerated; private boolean isWhitespace; public boolean getFeature(String feature) { if (XmlPullParser .FEATURE_PROCESS_NAMESPACES .equals(feature)) return processNsp; else return false; } public String getInputEncoding() { // should return someting depending on charSet here!!!!! return null; } 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; } 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.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: " + 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(); charSet = readInt(); // skip charset int strTabSize = readInt(); StringBuffer buf = new StringBuffer(strTabSize); for (int i = 0; i < strTabSize; i++) buf.append((char) readByte()); stringTable = buf.toString(); } 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 exception(String desc) throws XmlPullParserException { throw new XmlPullParserException(desc, this, null); } private final void nextImpl() throws IOException, XmlPullParserException { String s; if (type == END_TAG) { depth--; } if (degenerated) { type = XmlPullParser.END_TAG; return; } text = null; prefix = null; name = null; int id = peekId (); nextId = -2; switch (id) { case -1 : type = XmlPullParser.END_DOCUMENT; break; case Wbxml.SWITCH_PAGE : if (readByte() != 0) throw new IOException("Curr. only CP0 supported"); 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 : parseWapExtension(id); break; case Wbxml.PI : throw new RuntimeException("PI curr. not supp."); // readPI; // break; case Wbxml.STR_T : { type = TEXT; int pos = readInt(); int end = stringTable.indexOf('\0', pos); text = stringTable.substring(pos, end); } break; default : parseElement(id); } // } // while (next == null); // return next; } /** For handling wap extensions in attributes, overwrite this method, call super and return a corresponding TextEvent. */ public void parseWapExtension(int id) throws IOException, XmlPullParserException { type = WAP_EXTENSION; wapExtensionCode = id; switch (id) { case Wbxml.EXT_I_0 : case Wbxml.EXT_I_1 : case Wbxml.EXT_I_2 : wapExtensionData = readStrI(); break; case Wbxml.EXT_T_0 : case Wbxml.EXT_T_1 : case Wbxml.EXT_T_2 : wapExtensionData = new Integer(readInt()); break; case Wbxml.EXT_0 : case Wbxml.EXT_1 : case Wbxml.EXT_2 : break; case Wbxml.OPAQUE : { int len = readInt(); byte[] buf = new byte[len]; for (int i = 0; i < len; i++) // enhance with blockread! buf[i] = (byte) readByte(); wapExtensionData = buf; } // case OPAQUE } // SWITCH throw new IOException("illegal id!"); } public void readAttr() throws IOException { int id = readByte(); int i = 0; while (id != 1) { 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.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.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 : throw new RuntimeException("wap extension in attr not supported yet"); /* ParseEvent e = parseWapExtension(id); if (!(e.getType() != Xml.TEXT && e.getType() != Xml.WHITESPACE)) throw new RuntimeException("parse WapExtension must return Text Event in order to work inside Attributes!"); value.append(e.getText()); //value.append (handleExtension (id)); // skip EXT in ATTR //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(); } } private int peekId () throws IOException { if (nextId == -2) { nextId = in.read (); } return nextId; } String resolveId(String[] tab, int id) throws IOException { int idx = (id & 0x07f) - 5; if (idx == -1) return readStrT(); if (idx < 0 || tab == null || idx >= tab.length || tab[idx] == null) throw new IOException("id " + id + " undef."); return tab[idx]; } void parseElement(int id) throws IOException, XmlPullParserException { name = resolveId(tagTable, id & 0x03f); 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 { StringBuffer buf = new StringBuffer(); boolean wsp = true; while (true) { int i = in.read(); if (i == -1) throw new IOException("Unexpected EOF"); if (i == 0) break; if (i > 32) wsp = false; buf.append((char) i); } isWhitespace = wsp; return buf.toString(); } String readStrT() throws IOException { int pos = readInt(); int end = stringTable.indexOf('\0', pos); return stringTable.substring(pos, end); } /** Sets the tag table for a given page. * The first string in the array defines tag 5, the second tag 6 etc. * Currently, only page 0 is supported */ public void setTagTable(int page, String[] tagTable) { 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. * Currently, only page 0 is supported. Please use the * character '=' (without quote!) as delimiter * between the attribute name and the (start of the) value */ public void setAttrStartTable( int page, String[] attrStartTable) { this.attrStartTable = attrStartTable; if (page != 0) throw new RuntimeException("code pages curr. not supp."); } /** 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. * Currently, only page 0 is supported. */ public void setAttrValueTable( int page, String[] attrStartTable) { this.attrValueTable = attrStartTable; if (page != 0) throw new RuntimeException("code pages curr. not supp."); } }