1 /* 2 * Copyright (C) 2006 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.internal.util; 18 19 import org.xmlpull.v1.XmlSerializer; 20 21 import java.io.IOException; 22 import java.io.OutputStream; 23 import java.io.OutputStreamWriter; 24 import java.io.UnsupportedEncodingException; 25 import java.io.Writer; 26 import java.nio.ByteBuffer; 27 import java.nio.CharBuffer; 28 import java.nio.charset.Charset; 29 import java.nio.charset.CharsetEncoder; 30 import java.nio.charset.CoderResult; 31 import java.nio.charset.IllegalCharsetNameException; 32 import java.nio.charset.UnsupportedCharsetException; 33 34 /** 35 * This is a quick and dirty implementation of XmlSerializer that isn't horribly 36 * painfully slow like the normal one. It only does what is needed for the 37 * specific XML files being written with it. 38 */ 39 public class FastXmlSerializer implements XmlSerializer { 40 private static final String ESCAPE_TABLE[] = new String[] { 41 null, null, null, null, null, null, null, null, // 0-7 42 null, null, null, null, null, null, null, null, // 8-15 43 null, null, null, null, null, null, null, null, // 16-23 44 null, null, null, null, null, null, null, null, // 24-31 45 null, null, """, null, null, null, "&", null, // 32-39 46 null, null, null, null, null, null, null, null, // 40-47 47 null, null, null, null, null, null, null, null, // 48-55 48 null, null, null, null, "<", null, ">", null, // 56-63 49 }; 50 51 private static final int BUFFER_LEN = 8192; 52 53 private static String sSpace = " "; 54 55 private final char[] mText = new char[BUFFER_LEN]; 56 private int mPos; 57 58 private Writer mWriter; 59 60 private OutputStream mOutputStream; 61 private CharsetEncoder mCharset; 62 private ByteBuffer mBytes = ByteBuffer.allocate(BUFFER_LEN); 63 64 private boolean mIndent = false; 65 private boolean mInTag; 66 67 private int mNesting = 0; 68 private boolean mLineStart = true; 69 70 private void append(char c) throws IOException { 71 int pos = mPos; 72 if (pos >= (BUFFER_LEN-1)) { 73 flush(); 74 pos = mPos; 75 } 76 mText[pos] = c; 77 mPos = pos+1; 78 } 79 80 private void append(String str, int i, final int length) throws IOException { 81 if (length > BUFFER_LEN) { 82 final int end = i + length; 83 while (i < end) { 84 int next = i + BUFFER_LEN; 85 append(str, i, next<end ? BUFFER_LEN : (end-i)); 86 i = next; 87 } 88 return; 89 } 90 int pos = mPos; 91 if ((pos+length) > BUFFER_LEN) { 92 flush(); 93 pos = mPos; 94 } 95 str.getChars(i, i+length, mText, pos); 96 mPos = pos + length; 97 } 98 99 private void append(char[] buf, int i, final int length) throws IOException { 100 if (length > BUFFER_LEN) { 101 final int end = i + length; 102 while (i < end) { 103 int next = i + BUFFER_LEN; 104 append(buf, i, next<end ? BUFFER_LEN : (end-i)); 105 i = next; 106 } 107 return; 108 } 109 int pos = mPos; 110 if ((pos+length) > BUFFER_LEN) { 111 flush(); 112 pos = mPos; 113 } 114 System.arraycopy(buf, i, mText, pos, length); 115 mPos = pos + length; 116 } 117 118 private void append(String str) throws IOException { 119 append(str, 0, str.length()); 120 } 121 122 private void appendIndent(int indent) throws IOException { 123 indent *= 4; 124 if (indent > sSpace.length()) { 125 indent = sSpace.length(); 126 } 127 append(sSpace, 0, indent); 128 } 129 130 private void escapeAndAppendString(final String string) throws IOException { 131 final int N = string.length(); 132 final char NE = (char)ESCAPE_TABLE.length; 133 final String[] escapes = ESCAPE_TABLE; 134 int lastPos = 0; 135 int pos; 136 for (pos=0; pos<N; pos++) { 137 char c = string.charAt(pos); 138 if (c >= NE) continue; 139 String escape = escapes[c]; 140 if (escape == null) continue; 141 if (lastPos < pos) append(string, lastPos, pos-lastPos); 142 lastPos = pos + 1; 143 append(escape); 144 } 145 if (lastPos < pos) append(string, lastPos, pos-lastPos); 146 } 147 148 private void escapeAndAppendString(char[] buf, int start, int len) throws IOException { 149 final char NE = (char)ESCAPE_TABLE.length; 150 final String[] escapes = ESCAPE_TABLE; 151 int end = start+len; 152 int lastPos = start; 153 int pos; 154 for (pos=start; pos<end; pos++) { 155 char c = buf[pos]; 156 if (c >= NE) continue; 157 String escape = escapes[c]; 158 if (escape == null) continue; 159 if (lastPos < pos) append(buf, lastPos, pos-lastPos); 160 lastPos = pos + 1; 161 append(escape); 162 } 163 if (lastPos < pos) append(buf, lastPos, pos-lastPos); 164 } 165 166 public XmlSerializer attribute(String namespace, String name, String value) throws IOException, 167 IllegalArgumentException, IllegalStateException { 168 append(' '); 169 if (namespace != null) { 170 append(namespace); 171 append(':'); 172 } 173 append(name); 174 append("=\""); 175 176 escapeAndAppendString(value); 177 append('"'); 178 mLineStart = false; 179 return this; 180 } 181 182 public void cdsect(String text) throws IOException, IllegalArgumentException, 183 IllegalStateException { 184 throw new UnsupportedOperationException(); 185 } 186 187 public void comment(String text) throws IOException, IllegalArgumentException, 188 IllegalStateException { 189 throw new UnsupportedOperationException(); 190 } 191 192 public void docdecl(String text) throws IOException, IllegalArgumentException, 193 IllegalStateException { 194 throw new UnsupportedOperationException(); 195 } 196 197 public void endDocument() throws IOException, IllegalArgumentException, IllegalStateException { 198 flush(); 199 } 200 201 public XmlSerializer endTag(String namespace, String name) throws IOException, 202 IllegalArgumentException, IllegalStateException { 203 mNesting--; 204 if (mInTag) { 205 append(" />\n"); 206 } else { 207 if (mIndent && mLineStart) { 208 appendIndent(mNesting); 209 } 210 append("</"); 211 if (namespace != null) { 212 append(namespace); 213 append(':'); 214 } 215 append(name); 216 append(">\n"); 217 } 218 mLineStart = true; 219 mInTag = false; 220 return this; 221 } 222 223 public void entityRef(String text) throws IOException, IllegalArgumentException, 224 IllegalStateException { 225 throw new UnsupportedOperationException(); 226 } 227 228 private void flushBytes() throws IOException { 229 int position; 230 if ((position = mBytes.position()) > 0) { 231 mBytes.flip(); 232 mOutputStream.write(mBytes.array(), 0, position); 233 mBytes.clear(); 234 } 235 } 236 237 public void flush() throws IOException { 238 //Log.i("PackageManager", "flush mPos=" + mPos); 239 if (mPos > 0) { 240 if (mOutputStream != null) { 241 CharBuffer charBuffer = CharBuffer.wrap(mText, 0, mPos); 242 CoderResult result = mCharset.encode(charBuffer, mBytes, true); 243 while (true) { 244 if (result.isError()) { 245 throw new IOException(result.toString()); 246 } else if (result.isOverflow()) { 247 flushBytes(); 248 result = mCharset.encode(charBuffer, mBytes, true); 249 continue; 250 } 251 break; 252 } 253 flushBytes(); 254 mOutputStream.flush(); 255 } else { 256 mWriter.write(mText, 0, mPos); 257 mWriter.flush(); 258 } 259 mPos = 0; 260 } 261 } 262 263 public int getDepth() { 264 throw new UnsupportedOperationException(); 265 } 266 267 public boolean getFeature(String name) { 268 throw new UnsupportedOperationException(); 269 } 270 271 public String getName() { 272 throw new UnsupportedOperationException(); 273 } 274 275 public String getNamespace() { 276 throw new UnsupportedOperationException(); 277 } 278 279 public String getPrefix(String namespace, boolean generatePrefix) 280 throws IllegalArgumentException { 281 throw new UnsupportedOperationException(); 282 } 283 284 public Object getProperty(String name) { 285 throw new UnsupportedOperationException(); 286 } 287 288 public void ignorableWhitespace(String text) throws IOException, IllegalArgumentException, 289 IllegalStateException { 290 throw new UnsupportedOperationException(); 291 } 292 293 public void processingInstruction(String text) throws IOException, IllegalArgumentException, 294 IllegalStateException { 295 throw new UnsupportedOperationException(); 296 } 297 298 public void setFeature(String name, boolean state) throws IllegalArgumentException, 299 IllegalStateException { 300 if (name.equals("http://xmlpull.org/v1/doc/features.html#indent-output")) { 301 mIndent = true; 302 return; 303 } 304 throw new UnsupportedOperationException(); 305 } 306 307 public void setOutput(OutputStream os, String encoding) throws IOException, 308 IllegalArgumentException, IllegalStateException { 309 if (os == null) 310 throw new IllegalArgumentException(); 311 if (true) { 312 try { 313 mCharset = Charset.forName(encoding).newEncoder(); 314 } catch (IllegalCharsetNameException e) { 315 throw (UnsupportedEncodingException) (new UnsupportedEncodingException( 316 encoding).initCause(e)); 317 } catch (UnsupportedCharsetException e) { 318 throw (UnsupportedEncodingException) (new UnsupportedEncodingException( 319 encoding).initCause(e)); 320 } 321 mOutputStream = os; 322 } else { 323 setOutput( 324 encoding == null 325 ? new OutputStreamWriter(os) 326 : new OutputStreamWriter(os, encoding)); 327 } 328 } 329 330 public void setOutput(Writer writer) throws IOException, IllegalArgumentException, 331 IllegalStateException { 332 mWriter = writer; 333 } 334 335 public void setPrefix(String prefix, String namespace) throws IOException, 336 IllegalArgumentException, IllegalStateException { 337 throw new UnsupportedOperationException(); 338 } 339 340 public void setProperty(String name, Object value) throws IllegalArgumentException, 341 IllegalStateException { 342 throw new UnsupportedOperationException(); 343 } 344 345 public void startDocument(String encoding, Boolean standalone) throws IOException, 346 IllegalArgumentException, IllegalStateException { 347 append("<?xml version='1.0' encoding='utf-8' standalone='" 348 + (standalone ? "yes" : "no") + "' ?>\n"); 349 mLineStart = true; 350 } 351 352 public XmlSerializer startTag(String namespace, String name) throws IOException, 353 IllegalArgumentException, IllegalStateException { 354 if (mInTag) { 355 append(">\n"); 356 } 357 if (mIndent) { 358 appendIndent(mNesting); 359 } 360 mNesting++; 361 append('<'); 362 if (namespace != null) { 363 append(namespace); 364 append(':'); 365 } 366 append(name); 367 mInTag = true; 368 mLineStart = false; 369 return this; 370 } 371 372 public XmlSerializer text(char[] buf, int start, int len) throws IOException, 373 IllegalArgumentException, IllegalStateException { 374 if (mInTag) { 375 append(">"); 376 mInTag = false; 377 } 378 escapeAndAppendString(buf, start, len); 379 if (mIndent) { 380 mLineStart = buf[start+len-1] == '\n'; 381 } 382 return this; 383 } 384 385 public XmlSerializer text(String text) throws IOException, IllegalArgumentException, 386 IllegalStateException { 387 if (mInTag) { 388 append(">"); 389 mInTag = false; 390 } 391 escapeAndAppendString(text); 392 if (mIndent) { 393 mLineStart = text.length() > 0 && (text.charAt(text.length()-1) == '\n'); 394 } 395 return this; 396 } 397 398 } 399