Bring our kxml2 up to date with upstream.

Much of this is spurious whitespace changes, but there's some increased
"relaxation". I deliberately lost the Android-specific change that was
avoiding Runtime, since we do now have Runtime. I've added an Android-specific
change to comment out some System.out logging that's been added upstream.

I'd tell you the upstream revision number, but they're still using CVS, so
there isn't one.
diff --git a/xml/src/main/java/org/kxml2/io/KXmlParser.java b/xml/src/main/java/org/kxml2/io/KXmlParser.java
index 0727bc7..98aae04 100644
--- a/xml/src/main/java/org/kxml2/io/KXmlParser.java
+++ b/xml/src/main/java/org/kxml2/io/KXmlParser.java
@@ -33,7 +33,7 @@
 public class KXmlParser implements XmlPullParser {
 
     private Object location;
-    static final private String UNEXPECTED_EOF = "Unexpected EOF";
+	static final private String UNEXPECTED_EOF = "Unexpected EOF";
     static final private String ILLEGAL_TYPE = "Wrong event type";
     static final private int LEGACY = 999;
     static final private int XML_DECL = 998;
@@ -65,13 +65,14 @@
 
     // txtbuffer
 
+    /** Target buffer for storing incoming text (including aggregated resolved entities) */
     private char[] txtBuf = new char[128];
+    /** Write position  */
     private int txtPos;
 
     // Event-related
 
     private int type;
-    //private String text;
     private boolean isWhitespace;
     private String namespace;
     private String prefix;
@@ -80,7 +81,7 @@
     private boolean degenerated;
     private int attributeCount;
     private String[] attributes = new String[16];
-    private int stackMismatch = 0;
+//    private int stackMismatch = 0;
     private String error;
 
     /** 
@@ -95,12 +96,8 @@
     private boolean token;
 
     public KXmlParser() {
-        // BEGIN android-changed
-        // We don't have a Runtime class at this time.
-        // srcBuf =
-        //         new char[Runtime.getRuntime().freeMemory() >= 1048576 ? 8192 : 128];
-        srcBuf = new char[8192];
-        // END android-changed
+        srcBuf =
+            new char[Runtime.getRuntime().freeMemory() >= 1048576 ? 8192 : 128];
     }
 
     private final boolean isProp(String n1, boolean prop, String n2) {
@@ -263,39 +260,39 @@
         while (true) {
             attributeCount = -1;
 
-            // degenerated needs to be handled before error because of possible
-            // processor expectations(!)
+			// degenerated needs to be handled before error because of possible
+			// processor expectations(!)
 
-            if (degenerated) {
-                degenerated = false;
-                type = END_TAG;
-                return;
-            }
+			if (degenerated) {
+				degenerated = false;
+				type = END_TAG;
+				return;
+			}
 
 
             if (error != null) {
                 for (int i = 0; i < error.length(); i++)
                     push(error.charAt(i));
-                //                text = error;
+                //				text = error;
                 error = null;
                 type = COMMENT;
                 return;
             }
 
 
-            if (relaxed
-                && (stackMismatch > 0 || (peek(0) == -1 && depth > 0))) {
-                int sp = (depth - 1) << 2;
-                type = END_TAG;
-                namespace = elementStack[sp];
-                prefix = elementStack[sp + 1];
-                name = elementStack[sp + 2];
-                if (stackMismatch != 1)
-                    error = "missing end tag /" + name + " inserted";
-                if (stackMismatch > 0)
-                    stackMismatch--;
-                return;
-            }
+//            if (relaxed
+//                && (stackMismatch > 0 || (peek(0) == -1 && depth > 0))) {
+//                int sp = (depth - 1) << 2;
+//                type = END_TAG;
+//                namespace = elementStack[sp];
+//                prefix = elementStack[sp + 1];
+//                name = elementStack[sp + 2];
+//                if (stackMismatch != 1)
+//                    error = "missing end tag /" + name + " inserted";
+//                if (stackMismatch > 0)
+//                    stackMismatch--;
+//                return;
+//            }
 
             prefix = null;
             name = null;
@@ -327,7 +324,7 @@
                         if (isWhitespace)
                             type = IGNORABLE_WHITESPACE;
                         // make exception switchable for instances.chg... !!!!
-                        //    else 
+                        //	else 
                         //    exception ("text '"+getText ()+"' not allowed outside root element");
                     }
                     return;
@@ -458,7 +455,7 @@
                 prev = c;
             }
 
-            if (term == '-' && prev == '-')
+            if (term == '-' && prev == '-' && !relaxed)
                 error("illegal comment delimiter: --->");
 
             read();
@@ -529,28 +526,30 @@
             return;
         }
 
-        if (!name.equals(elementStack[sp + 3])) {
+        if (!relaxed) {
+          if (!name.equals(elementStack[sp + 3])) {
             error("expected: /" + elementStack[sp + 3] + " read: " + name);
 
-            // become case insensitive in relaxed mode
+			// become case insensitive in relaxed mode
 
-            int probe = sp;
-            while (probe >= 0 && !name.toLowerCase().equals(elementStack[probe + 3].toLowerCase())) {
-                stackMismatch++;
-                probe -= 4;
-            }
-
-            if (probe < 0) {
-                stackMismatch = 0;
-                //            text = "unexpected end tag ignored";
-                type = COMMENT;
-                return;
-            }
+//            int probe = sp;
+//            while (probe >= 0 && !name.toLowerCase().equals(elementStack[probe + 3].toLowerCase())) {
+//                stackMismatch++;
+//                probe -= 4;
+//            }
+//
+//            if (probe < 0) {
+//                stackMismatch = 0;
+//                //			text = "unexpected end tag ignored";
+//                type = COMMENT;
+//                return;
+//            }
         }
 
         namespace = elementStack[sp];
         prefix = elementStack[sp + 1];
         name = elementStack[sp + 2];
+        }
     }
 
     private final int peekType() throws IOException {
@@ -661,8 +660,10 @@
             skip();
 
             if (peek(0) != '=') {
-                error("Attr.value missing f. "+attrName);
-                attributes[i] = "1";
+            	if(!relaxed){
+            		error("Attr.value missing f. "+attrName);
+            	}
+                attributes[i] = attrName;
             }
             else {
                 read('=');
@@ -670,12 +671,14 @@
                 int delimiter = peek(0);
 
                 if (delimiter != '\'' && delimiter != '"') {
-                    error("attr value delimiter missing!");
+                	if(!relaxed){
+                		error("attr value delimiter missing!");
+                	}
                     delimiter = ' ';
                 }
-                else 
-                    read();
-                
+				else 
+					read();
+				
                 int p = txtPos;
                 pushText(delimiter, true);
 
@@ -701,14 +704,14 @@
         nspCounts[depth] = nspCounts[depth - 1];
 
         /*
-                if(!relaxed){
+        		if(!relaxed){
                 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();
@@ -733,9 +736,11 @@
         int pos = txtPos;
 
         while (true) {
-            int c = read();
-            if (c == ';')
-                break;
+            int c = peek(0);
+            if (c == ';') {
+              read();
+              break;
+            }
             if (c < 128
                 && (c < '0' || c > '9')
                 && (c < 'a' || c > 'z')
@@ -743,16 +748,21 @@
                 && c != '_'
                 && c != '-'
                 && c != '#') {
-                if(!relaxed){
-                    error("unterminated entity ref");
-                }
+            	if(!relaxed){
+            		error("unterminated entity ref");
+            	}
+            	
+            	// BEGIN android-removed: avoid log spam.
+            	// System.out.println("broken entitiy: "+get(pos-1));
+            	// END android-removed
+            	
                 //; ends with:"+(char)c);           
-                if (c != -1)
-                    push(c);
+//                if (c != -1)
+//                    push(c);
                 return;
             }
 
-            push(c);
+            push(read());
         }
 
         String code = get(pos);
@@ -843,10 +853,10 @@
             result = peek[0];
             peek[0] = peek[1];
         }
-        //        else {
-        //            result = peek[0]; 
-        //            System.arraycopy (peek, 1, peek, 0, peekCount-1);
-        //        }
+        //		else {
+        //			result = peek[0]; 
+        //			System.arraycopy (peek, 1, peek, 0, peekCount-1);
+        //		}
         peekCount--;
 
         column++;
@@ -1121,8 +1131,8 @@
             return version;
         if (isProp(property, true, "xmldecl-standalone"))
             return standalone;
-        if (isProp(property, true, "location"))            
-            return location != null ? location : reader.toString();
+		if (isProp(property, true, "location"))            
+			return location != null ? location : reader.toString();
         return null;
     }
 
@@ -1202,15 +1212,15 @@
             buf.append(text);
         }
 
-        buf.append("@"+line + ":" + column);
-        if(location != null){
-            buf.append(" in ");
-            buf.append(location);
-        }
-        else if(reader != null){
-            buf.append(" in ");
-            buf.append(reader.toString());
-        }
+		buf.append("@"+line + ":" + column);
+		if(location != null){
+			buf.append(" in ");
+			buf.append(location);
+		}
+		else if(reader != null){
+			buf.append(" in ");
+			buf.append(reader.toString());
+		}
         return buf.toString();
     }
 
@@ -1330,7 +1340,7 @@
             nextImpl();
             if (type < minType)
                 minType = type;
-            //        if (curr <= TEXT) type = curr; 
+            //	    if (curr <= TEXT) type = curr; 
         }
         while (minType > ENTITY_REF // ignorable
             || (minType >= TEXT && peekType() >= TEXT));
@@ -1411,9 +1421,9 @@
     public void setProperty(String property, Object value)
         throws XmlPullParserException {
         if(isProp(property, true, "location"))
-            location = value;
+        	location = value;
         else
-            throw new XmlPullParserException("unsupported property: " + property);
+	        throw new XmlPullParserException("unsupported property: " + property);
     }
 
     /**
@@ -1422,7 +1432,7 @@
       * parser will be positioned on corresponding END_TAG. 
       */
 
-    //    Implementation copied from Alek's mail... 
+    //	Implementation copied from Alek's mail... 
 
     public void skipSubTree() throws XmlPullParserException, IOException {
         require(START_TAG, null, null);
diff --git a/xml/src/main/java/org/kxml2/wap/WbxmlSerializer.java b/xml/src/main/java/org/kxml2/wap/WbxmlSerializer.java
index 8c1b598..a977386 100644
--- a/xml/src/main/java/org/kxml2/wap/WbxmlSerializer.java
+++ b/xml/src/main/java/org/kxml2/wap/WbxmlSerializer.java
@@ -30,15 +30,11 @@
 // TODO: make some of the "direct" WBXML token writing methods public??
 
 /**
- * A class for writing WBXML.
- *
+ * A class for writing WBXML. Does not support namespaces yet.
  */
-
-
-
 public class WbxmlSerializer implements XmlSerializer {
     
-    
+	
     Hashtable stringTable = new Hashtable();
     
     OutputStream out;
@@ -61,7 +57,14 @@
     
     private String encoding;
     
+    private boolean headerSent = false;
     
+    /**
+     * Write an attribute. 
+     * Calls to attribute() MUST follow a call to startTag() immediately. 
+     * If there is no prefix defined for the given namespace, 
+     * a prefix will be defined automatically.
+     */
     public XmlSerializer attribute(String namespace, String name, String value) {
         attributes.addElement(name);
         attributes.addElement(value);
@@ -73,45 +76,71 @@
         text (cdsect);
     }
     
-    
-    
-    /* silently ignore comment */
-    
+    /**
+     * Add comment. Ignore for WBXML.
+     */
     public void comment (String comment) {
+        // silently ignore comment
     }
     
-    
+    /**
+     * Docdecl isn't supported for WBXML.
+     */
     public void docdecl (String docdecl) {
         throw new RuntimeException ("Cannot write docdecl for WBXML");
     }
     
-    
+    /**
+     * EntityReference not supported for WBXML.
+     */
     public void entityRef (String er) {
         throw new RuntimeException ("EntityReference not supported for WBXML");
     }
     
+    /**
+     * Return current tag depth.
+     */
     public int getDepth() {
         return depth;
     }
     
-    
+    /**
+     * Return the current value of the feature with given name.
+     */ 
     public boolean getFeature (String name) {
         return false;
     }
     
+    /**
+     * Returns the namespace URI of the current element as set by startTag().
+     * Namespaces is not yet implemented.
+     */
     public String getNamespace() {
-        throw new RuntimeException("NYI");
+        // Namespaces is not yet implemented. So only null can be setted
+        return null;
     }
     
+    /**
+     * Returns the name of the current element as set by startTag(). 
+     * It can only be null before first call to startTag() or when last endTag() 
+     * is called to close first startTag().
+     */
     public String getName() {
-        throw new RuntimeException("NYI");
+        return pending;
     }
     
+    /**
+     * Prefix for namespace not supported for WBXML. Not yet implemented.
+     */
     public String getPrefix(String nsp, boolean create) {
         throw new RuntimeException ("NYI");
     }
     
-    
+    /**
+     * Look up the value of a property. 
+     * @param name The name of property. Name is any fully-qualified URI.
+     * @return The value of named property.
+     */
     public Object getProperty (String name) {
         return null;
     }
@@ -119,31 +148,34 @@
     public void ignorableWhitespace (String sp) {
     }
     
-    
+    /**
+     * Finish writing. 
+     * All unclosed start tags will be closed and output will be flushed. 
+     * After calling this method no more output can be serialized until 
+     * next call to setOutput().
+     */
     public void endDocument() throws IOException {
-        writeInt(out, stringTableBuf.size());
+        flush();
+    }
         
-        // write StringTable
+    /**
+     * Write all pending output to the stream. 
+     * After first call string table willn't be used and you can't add tag
+     * which is not in tag table.
+     */
+    public void flush() throws IOException {
+        checkPending(false);
         
-        out.write(stringTableBuf.toByteArray());
-        
-        // write buf
+        if (!headerSent) {
+            writeInt(out, stringTableBuf.size());
+            out.write(stringTableBuf.toByteArray());
+            headerSent = true;
+        }
         
         out.write(buf.toByteArray());
-        
-        // ready!
-        
-        out.flush();
+        buf.reset();
     }
     
-    
-    /** ATTENTION: flush cannot work since Wbxml documents require
-      buffering. Thus, this call does nothing. */
-    
-    public void flush() {
-    }
-    
-    
     public void checkPending(boolean degenerated) throws IOException {
         if (pending == null)
             return;
@@ -154,27 +186,20 @@
         
         // if no entry in known table, then add as literal
         if (idx == null) {
-            buf.write(
-            len == 0
-            ? (degenerated ? Wbxml.LITERAL : Wbxml.LITERAL_C)
-            : (degenerated ? Wbxml.LITERAL_A : Wbxml.LITERAL_AC));
+            buf.write(len == 0
+                ? (degenerated ? Wbxml.LITERAL : Wbxml.LITERAL_C)
+                    : (degenerated ? Wbxml.LITERAL_A : Wbxml.LITERAL_AC));
             
             writeStrT(pending, false);
-        }
-        else {
+        } else {
             if(idx[0] != tagPage){
                 tagPage=idx[0];
-                buf.write(Wbxml.SWITCH_PAGE);
+				buf.write(Wbxml.SWITCH_PAGE);
                 buf.write(tagPage);
             }
-            
-            buf.write(
-            len == 0
-            ? (degenerated ? idx[1] : idx[1] | 64)
-            : (degenerated
-            ? idx[1] | 128
-            : idx[1] | 192));
-           
+            buf.write(len == 0
+                ? (degenerated ? idx[1] : idx[1] | 64)
+                    : (degenerated ? idx[1] | 128 : idx[1] | 192));
         }
         
         for (int i = 0; i < len;) {
@@ -186,7 +211,7 @@
             }
             else {
                 if(idx[0] != attrPage){
-                        attrPage = idx[0];
+                  attrPage = idx[0];
                     buf.write(0);
                     buf.write(attrPage);
                 }
@@ -198,9 +223,9 @@
             }
             else {
                 if(idx[0] != attrPage){
-                        attrPage = idx[0];
+                    attrPage = idx[0];
                     buf.write(0);
-                    buf.write(attrPage);                    
+                    buf.write(attrPage);					
                 }
                 buf.write(idx[1]);
             }
@@ -214,60 +239,79 @@
         attributes.removeAllElements();
     }
     
-    
+    /**
+     * Not Yet Implemented.
+     */
     public void processingInstruction(String pi) {
         throw new RuntimeException ("PI NYI");
     }
     
-    
+    /**
+     * Set feature identified by name. There are no supported functions.
+     */
     public void setFeature(String name, boolean value) {
         throw new IllegalArgumentException ("unknown feature "+name);
     }
     
-    
-    
+    /**
+     * Set the output to the given writer. Wbxml requires an OutputStream.
+     */
     public void setOutput (Writer writer) {
         throw new RuntimeException ("Wbxml requires an OutputStream!");
     }
     
+    /**
+     * Set to use binary output stream with given encoding.
+     */
     public void setOutput (OutputStream out, String encoding) throws IOException {
         
-        this.encoding = encoding == null ? "UTF-8" : encoding;
+    	this.encoding = encoding == null ? "UTF-8" : encoding;
         this.out = out;
         
         buf = new ByteArrayOutputStream();
         stringTableBuf = new ByteArrayOutputStream();
+        headerSent = false;
         
         // ok, write header
     }
     
-    
+    /**
+     * Binds the given prefix to the given namespace. Not yet implemented.
+     */
     public void setPrefix(String prefix, String nsp) {
         throw new RuntimeException("NYI");
     }
     
+    /**
+     * Set the value of a property. There are no supported properties.
+     */
     public void setProperty(String property, Object value) {
         throw new IllegalArgumentException ("unknown property "+property);
     }
     
-    
-    public void startDocument(String s, Boolean b) throws IOException{
+    /**
+     * Write version and encoding information.
+     * This method can only be called just after setOutput.
+     * @param encoding Document encoding. Default is UTF-8.
+     * @param standalone Not used in WBXML.
+     */
+    public void startDocument(String encoding, Boolean standalone) throws IOException {
         out.write(0x03); // version 1.3
         // http://www.openmobilealliance.org/tech/omna/omna-wbxml-public-docid.htm
         out.write(0x01); // unknown or missing public identifier
 
         // default encoding is UTF-8
         
-        if(s != null){
-            encoding = s;
+        if(encoding != null){
+            this.encoding = encoding;
         }
         
-        if (encoding.toUpperCase().equals("UTF-8")){
+        if (this.encoding.toUpperCase().equals("UTF-8")){
             out.write(106);
-        }else if (encoding.toUpperCase().equals("ISO-8859-1")){
+        }else if (this.encoding.toUpperCase().equals("ISO-8859-1")){
             out.write(0x04);
         }else{
-            throw new UnsupportedEncodingException(s);
+            throw new UnsupportedEncodingException(encoding);
         }
     }
     
@@ -287,121 +331,114 @@
     }
     
     public XmlSerializer text(char[] chars, int start, int len) throws IOException {
-
-        checkPending(false);
-        
+    	checkPending(false);
         writeStr(new String(chars, start, len));
-        
         return this;
     }
     
     public XmlSerializer text(String text) throws IOException {
-        
         checkPending(false);
-        
         writeStr(text);
-    
         return this;
     }
     
-
-    /** Used in text() and attribute() to write text */
-    
+    /** 
+     * Used in text() and attribute() to write text.
+     */ 
     private void writeStr(String text) throws IOException{
         int p0 = 0;
-        int lastCut = 0;
-        int len = text.length();
-        
-        while(p0 < len){
-            while(p0 < len && text.charAt(p0) < 'A' ){ // skip interpunctation
-                p0++;
-            }
-            int p1 = p0;
-            while(p1 < len && text.charAt(p1) >= 'A'){
-                p1++;
-            }
-            
-            if(p1 - p0 > 10) {
-
-                if(p0 > lastCut && text.charAt(p0-1) == ' ' 
-                    && stringTable.get(text.substring(p0, p1)) == null){
-                    
-                       buf.write(Wbxml.STR_T);
-                       writeStrT(text.substring(lastCut, p1), false);
-                }
-                else {
-
-                    if(p0 > lastCut && text.charAt(p0-1) == ' '){
-                        p0--;
-                    }
-
-                    if(p0 > lastCut){
-                        buf.write(Wbxml.STR_T);
-                        writeStrT(text.substring(lastCut, p0), false);
-                    }
-                    buf.write(Wbxml.STR_T);
-                    writeStrT(text.substring(p0, p1), true);
-                }
-                lastCut = p1;
-            }
-            p0 = p1;
+    	int lastCut = 0;
+    	int len = text.length();
+    	
+        if (headerSent) {
+          writeStrI(buf, text);
+          return;
         }
+         
+    	while(p0 < len){
+    	    while(p0 < len && text.charAt(p0) < 'A' ){ // skip interpunctation
+    	        p0++;
+    	    }
+    	    int p1 = p0;
+    	    while (p1 < len && text.charAt(p1) >= 'A'){
+    	        p1++;
+    	    }
+    		
+    	    if (p1 - p0 > 10) {
+    	        if (p0 > lastCut && text.charAt(p0-1) == ' ' 
+    	            && stringTable.get(text.substring(p0, p1)) == null){
+    	            buf.write(Wbxml.STR_T);
+    	            writeStrT(text.substring(lastCut, p1), false);
+    	        }
+    	        else {
+    	            if(p0 > lastCut && text.charAt(p0-1) == ' '){
+    	                p0--;
+    	            }
 
-        if(lastCut < len){
-            buf.write(Wbxml.STR_T);
-            writeStrT(text.substring(lastCut, len), false);
-        }
+    	            if(p0 > lastCut){
+    	                buf.write(Wbxml.STR_T);
+    	                writeStrT(text.substring(lastCut, p0), false);
+    	            }
+    	            buf.write(Wbxml.STR_T);
+    	            writeStrT(text.substring(p0, p1), true);
+    	        }
+    	        lastCut = p1;
+    	    }
+    	    p0 = p1;
+    	}
+
+    	if(lastCut < len){
+    	  buf.write(Wbxml.STR_T);
+    	  writeStrT(text.substring(lastCut, len), false);
+    	}
     }
     
     
 
     public XmlSerializer endTag(String namespace, String name) throws IOException {
-        
         //        current = current.prev;
-        
-        if (pending != null)
+        if (pending != null) {
             checkPending(true);
-        else
+        } else {
             buf.write(Wbxml.END);
-        
+        }
         depth--;
-        
         return this;
     }
     
     /** 
-     * @throws IOException */
-    
+     * @throws IOException 
+     */
     public void writeWapExtension(int type, Object data) throws IOException {
         checkPending(false);
-        buf.write(type);
-        switch(type){
-        case Wbxml.EXT_0:
-        case Wbxml.EXT_1:
-        case Wbxml.EXT_2:
-            break;
-        
-        case Wbxml.OPAQUE:
-            byte[] bytes = (byte[]) data;
-            writeInt(buf, bytes.length);
-            buf.write(bytes);
-            break;
-            
-        case Wbxml.EXT_I_0:
-        case Wbxml.EXT_I_1:
-        case Wbxml.EXT_I_2:
-            writeStrI(buf, (String) data);
-            break;
+    	buf.write(type);
+    	switch(type){
+    	case Wbxml.EXT_0:
+    	case Wbxml.EXT_1:
+    	case Wbxml.EXT_2:
+    	    break;
+    	
+    	case Wbxml.OPAQUE:
+    	    byte[] bytes = (byte[]) data;
+    	    writeInt(buf, bytes.length);
+    	    buf.write(bytes);
+    	    break;
+    		
+    	case Wbxml.EXT_I_0:
+    	case Wbxml.EXT_I_1:
+    	case Wbxml.EXT_I_2:
+    	  writeStrI(buf, (String) data);
+    	  break;
 
-        case Wbxml.EXT_T_0:
-        case Wbxml.EXT_T_1:
-        case Wbxml.EXT_T_2:
-            writeStrT((String) data, false);
-            break;
-            
-        default: 
-            throw new IllegalArgumentException();
-        }
+    	case Wbxml.EXT_T_0:
+    	case Wbxml.EXT_T_1:
+    	case Wbxml.EXT_T_2:
+    	  writeStrT((String) data, false);
+    	  break;
+    		
+    	default: 
+    	  throw new IllegalArgumentException();
+    	}
     }
     
     // ------------- internal methods --------------------------
@@ -423,49 +460,58 @@
     }
     
     void writeStrI(OutputStream out, String s) throws IOException {
-        byte[] data = s.getBytes(encoding);
-        out.write(data);
+    	byte[] data = s.getBytes(encoding);
+    	out.write(data);
         out.write(0);
     }
     
     private final void writeStrT(String s, boolean mayPrependSpace) throws IOException {
         
-        Integer idx = (Integer) stringTable.get(s);
-        
-        if (idx != null) {
-            writeInt(buf, idx.intValue());
+        Integer idx = (Integer) stringTable.get(s);        
+        writeInt(buf, idx == null 
+            ? addToStringTable(s, mayPrependSpace)
+                : idx.intValue());
+    }
+    
+    
+    /**
+     * Add string to string table. Not permitted after string table has been flushed. 
+     * 
+     * @param s string to be added to the string table
+     * @param mayPrependSpace is set, a space is prepended to the string to archieve better compression results
+     * @return offset of s in the string table
+     */
+    public int addToStringTable(String s, boolean mayPrependSpace) throws IOException {
+        if (headerSent) {
+            throw new IOException("stringtable sent");
         }
-        else{
-            int i = stringTableBuf.size();
-            if(s.charAt(0) >= '0' && mayPrependSpace){
-                s = ' ' + s;
-                writeInt(buf, i+1);
-            }
-            else{
-                writeInt(buf, i);
-            }
-            
-               stringTable.put(s, new Integer(i));
-               if(s.charAt(0) == ' '){
-                   stringTable.put(s.substring(1), new Integer(i+1));
-               }
-               int j = s.lastIndexOf(' ');
-               if(j > 1){
-                   stringTable.put(s.substring(j), new Integer(i+j));
-                   stringTable.put(s.substring(j+1), new Integer(i+j+1));
-               }
+        
+        int i = stringTableBuf.size();
+        int offset = i;
+        if(s.charAt(0) >= '0' && mayPrependSpace){
+            s = ' ' + s;
+            offset++; 
+        }
+        
+        stringTable.put(s, new Integer(i));
+        if(s.charAt(0) == ' '){
+            stringTable.put(s.substring(1), new Integer(i+1));
+        }
+        int j = s.lastIndexOf(' ');
+        if(j > 1){
+                stringTable.put(s.substring(j), new Integer(i+j));
+                stringTable.put(s.substring(j+1), new Integer(i+j+1));
+        }
                 
-            writeStrI(stringTableBuf, s);
-            stringTableBuf.flush();
-        }
-        
+        writeStrI(stringTableBuf, s);
+        stringTableBuf.flush();
+        return offset;
     }
     
     /**
      * 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[] tagTable) {
         // TODO: clear entries in tagTable?
         
@@ -499,6 +545,7 @@
      * 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.
+     * Must be called BEFORE use attribute(), flush() etc.
      */
     public void setAttrValueTable(int page, String[] attrValueTable) {
         // clear entries in this.table!