001: /* Copyright (c) 2002,2003, Stefan Haustein, Oberhausen, Rhld., Germany
002: *
003: * Permission is hereby granted, free of charge, to any person obtaining a copy
004: * of this software and associated documentation files (the "Software"), to deal
005: * in the Software without restriction, including without limitation the rights
006: * to use, copy, modify, merge, publish, distribute, sublicense, and/or
007: * sell copies of the Software, and to permit persons to whom the Software is
008: * furnished to do so, subject to the following conditions:
009: *
010: * The above copyright notice and this permission notice shall be included in
011: * all copies or substantial portions of the Software.
012: *
013: * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
014: * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
015: * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
016: * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
017: * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
018: * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
019: * IN THE SOFTWARE. */
020:
021: //Contributors: Jonathan Cox, Bogdan Onoiu, Jerry Tian
022: package org.kxml2.wap;
023:
024: import java.io.*;
025: import java.util.*;
026:
027: import org.xmlpull.v1.*;
028:
029: // TODO: make some of the "direct" WBXML token writing methods public??
030:
031: /**
032: * A class for writing WBXML.
033: *
034: */
035:
036: public class WbxmlSerializer implements XmlSerializer {
037:
038: Hashtable stringTable = new Hashtable();
039:
040: OutputStream out;
041:
042: ByteArrayOutputStream buf = new ByteArrayOutputStream();
043: ByteArrayOutputStream stringTableBuf = new ByteArrayOutputStream();
044:
045: String pending;
046: int depth;
047: String name;
048: String namespace;
049: Vector attributes = new Vector();
050:
051: Hashtable attrStartTable = new Hashtable();
052: Hashtable attrValueTable = new Hashtable();
053: Hashtable tagTable = new Hashtable();
054:
055: private int attrPage;
056: private int tagPage;
057:
058: private String encoding;
059:
060: public XmlSerializer attribute(String namespace, String name,
061: String value) {
062: attributes.addElement(name);
063: attributes.addElement(value);
064: return this ;
065: }
066:
067: public void cdsect(String cdsect) throws IOException {
068: text(cdsect);
069: }
070:
071: /* silently ignore comment */
072:
073: public void comment(String comment) {
074: }
075:
076: public void docdecl(String docdecl) {
077: throw new RuntimeException("Cannot write docdecl for WBXML");
078: }
079:
080: public void entityRef(String er) {
081: throw new RuntimeException(
082: "EntityReference not supported for WBXML");
083: }
084:
085: public int getDepth() {
086: return depth;
087: }
088:
089: public boolean getFeature(String name) {
090: return false;
091: }
092:
093: public String getNamespace() {
094: throw new RuntimeException("NYI");
095: }
096:
097: public String getName() {
098: throw new RuntimeException("NYI");
099: }
100:
101: public String getPrefix(String nsp, boolean create) {
102: throw new RuntimeException("NYI");
103: }
104:
105: public Object getProperty(String name) {
106: return null;
107: }
108:
109: public void ignorableWhitespace(String sp) {
110: }
111:
112: public void endDocument() throws IOException {
113: writeInt(out, stringTableBuf.size());
114:
115: // write StringTable
116:
117: out.write(stringTableBuf.toByteArray());
118:
119: // write buf
120:
121: out.write(buf.toByteArray());
122:
123: // ready!
124:
125: out.flush();
126: }
127:
128: /** ATTENTION: flush cannot work since Wbxml documents require
129: buffering. Thus, this call does nothing. */
130:
131: public void flush() {
132: }
133:
134: public void checkPending(boolean degenerated) throws IOException {
135: if (pending == null)
136: return;
137:
138: int len = attributes.size();
139:
140: int[] idx = (int[]) tagTable.get(pending);
141:
142: // if no entry in known table, then add as literal
143: if (idx == null) {
144: buf.write(len == 0 ? (degenerated ? Wbxml.LITERAL
145: : Wbxml.LITERAL_C) : (degenerated ? Wbxml.LITERAL_A
146: : Wbxml.LITERAL_AC));
147:
148: writeStrT(pending, false);
149: } else {
150: if (idx[0] != tagPage) {
151: tagPage = idx[0];
152: buf.write(Wbxml.SWITCH_PAGE);
153: buf.write(tagPage);
154: }
155:
156: buf.write(len == 0 ? (degenerated ? idx[1] : idx[1] | 64)
157: : (degenerated ? idx[1] | 128 : idx[1] | 192));
158:
159: }
160:
161: for (int i = 0; i < len;) {
162: idx = (int[]) attrStartTable.get(attributes.elementAt(i));
163:
164: if (idx == null) {
165: buf.write(Wbxml.LITERAL);
166: writeStrT((String) attributes.elementAt(i), false);
167: } else {
168: if (idx[0] != attrPage) {
169: attrPage = idx[0];
170: buf.write(0);
171: buf.write(attrPage);
172: }
173: buf.write(idx[1]);
174: }
175: idx = (int[]) attrValueTable.get(attributes.elementAt(++i));
176: if (idx == null) {
177: writeStr((String) attributes.elementAt(i));
178: } else {
179: if (idx[0] != attrPage) {
180: attrPage = idx[0];
181: buf.write(0);
182: buf.write(attrPage);
183: }
184: buf.write(idx[1]);
185: }
186: ++i;
187: }
188:
189: if (len > 0)
190: buf.write(Wbxml.END);
191:
192: pending = null;
193: attributes.removeAllElements();
194: }
195:
196: public void processingInstruction(String pi) {
197: throw new RuntimeException("PI NYI");
198: }
199:
200: public void setFeature(String name, boolean value) {
201: throw new IllegalArgumentException("unknown feature " + name);
202: }
203:
204: public void setOutput(Writer writer) {
205: throw new RuntimeException("Wbxml requires an OutputStream!");
206: }
207:
208: public void setOutput(OutputStream out, String encoding)
209: throws IOException {
210:
211: this .encoding = encoding == null ? "UTF-8" : encoding;
212: this .out = out;
213:
214: buf = new ByteArrayOutputStream();
215: stringTableBuf = new ByteArrayOutputStream();
216:
217: // ok, write header
218: }
219:
220: public void setPrefix(String prefix, String nsp) {
221: throw new RuntimeException("NYI");
222: }
223:
224: public void setProperty(String property, Object value) {
225: throw new IllegalArgumentException("unknown property "
226: + property);
227: }
228:
229: public void startDocument(String s, Boolean b) throws IOException {
230: out.write(0x03); // version 1.3
231: // http://www.openmobilealliance.org/tech/omna/omna-wbxml-public-docid.htm
232: out.write(0x01); // unknown or missing public identifier
233:
234: // default encoding is UTF-8
235:
236: if (s != null) {
237: encoding = s;
238: }
239:
240: if (encoding.toUpperCase().equals("UTF-8")) {
241: out.write(106);
242: } else if (encoding.toUpperCase().equals("ISO-8859-1")) {
243: out.write(0x04);
244: } else {
245: throw new UnsupportedEncodingException(s);
246: }
247: }
248:
249: public XmlSerializer startTag(String namespace, String name)
250: throws IOException {
251:
252: if (namespace != null && !"".equals(namespace))
253: throw new RuntimeException("NSP NYI");
254:
255: //current = new State(current, prefixMap, name);
256:
257: checkPending(false);
258: pending = name;
259: depth++;
260:
261: return this ;
262: }
263:
264: public XmlSerializer text(char[] chars, int start, int len)
265: throws IOException {
266:
267: checkPending(false);
268:
269: writeStr(new String(chars, start, len));
270:
271: return this ;
272: }
273:
274: public XmlSerializer text(String text) throws IOException {
275:
276: checkPending(false);
277:
278: writeStr(text);
279:
280: return this ;
281: }
282:
283: /** Used in text() and attribute() to write text */
284:
285: private void writeStr(String text) throws IOException {
286: int p0 = 0;
287: int lastCut = 0;
288: int len = text.length();
289:
290: while (p0 < len) {
291: while (p0 < len && text.charAt(p0) < 'A') { // skip interpunctation
292: p0++;
293: }
294: int p1 = p0;
295: while (p1 < len && text.charAt(p1) >= 'A') {
296: p1++;
297: }
298:
299: if (p1 - p0 > 10) {
300:
301: if (p0 > lastCut
302: && text.charAt(p0 - 1) == ' '
303: && stringTable.get(text.substring(p0, p1)) == null) {
304:
305: buf.write(Wbxml.STR_T);
306: writeStrT(text.substring(lastCut, p1), false);
307: } else {
308:
309: if (p0 > lastCut && text.charAt(p0 - 1) == ' ') {
310: p0--;
311: }
312:
313: if (p0 > lastCut) {
314: buf.write(Wbxml.STR_T);
315: writeStrT(text.substring(lastCut, p0), false);
316: }
317: buf.write(Wbxml.STR_T);
318: writeStrT(text.substring(p0, p1), true);
319: }
320: lastCut = p1;
321: }
322: p0 = p1;
323: }
324:
325: if (lastCut < len) {
326: buf.write(Wbxml.STR_T);
327: writeStrT(text.substring(lastCut, len), false);
328: }
329: }
330:
331: public XmlSerializer endTag(String namespace, String name)
332: throws IOException {
333:
334: // current = current.prev;
335:
336: if (pending != null)
337: checkPending(true);
338: else
339: buf.write(Wbxml.END);
340:
341: depth--;
342:
343: return this ;
344: }
345:
346: /**
347: * @throws IOException */
348:
349: public void writeWapExtension(int type, Object data)
350: throws IOException {
351: checkPending(false);
352: buf.write(type);
353: switch (type) {
354: case Wbxml.EXT_0:
355: case Wbxml.EXT_1:
356: case Wbxml.EXT_2:
357: break;
358:
359: case Wbxml.OPAQUE:
360: byte[] bytes = (byte[]) data;
361: writeInt(buf, bytes.length);
362: buf.write(bytes);
363: break;
364:
365: case Wbxml.EXT_I_0:
366: case Wbxml.EXT_I_1:
367: case Wbxml.EXT_I_2:
368: writeStrI(buf, (String) data);
369: break;
370:
371: case Wbxml.EXT_T_0:
372: case Wbxml.EXT_T_1:
373: case Wbxml.EXT_T_2:
374: writeStrT((String) data, false);
375: break;
376:
377: default:
378: throw new IllegalArgumentException();
379: }
380: }
381:
382: // ------------- internal methods --------------------------
383:
384: static void writeInt(OutputStream out, int i) throws IOException {
385: byte[] buf = new byte[5];
386: int idx = 0;
387:
388: do {
389: buf[idx++] = (byte) (i & 0x7f);
390: i = i >> 7;
391: } while (i != 0);
392:
393: while (idx > 1) {
394: out.write(buf[--idx] | 0x80);
395: }
396: out.write(buf[0]);
397: }
398:
399: void writeStrI(OutputStream out, String s) throws IOException {
400: byte[] data = s.getBytes(encoding);
401: out.write(data);
402: out.write(0);
403: }
404:
405: private final void writeStrT(String s, boolean mayPrependSpace)
406: throws IOException {
407:
408: Integer idx = (Integer) stringTable.get(s);
409:
410: if (idx != null) {
411: writeInt(buf, idx.intValue());
412: } else {
413: int i = stringTableBuf.size();
414: if (s.charAt(0) >= '0' && mayPrependSpace) {
415: s = ' ' + s;
416: writeInt(buf, i + 1);
417: } else {
418: writeInt(buf, i);
419: }
420:
421: stringTable.put(s, new Integer(i));
422: if (s.charAt(0) == ' ') {
423: stringTable.put(s.substring(1), new Integer(i + 1));
424: }
425: int j = s.lastIndexOf(' ');
426: if (j > 1) {
427: stringTable.put(s.substring(j), new Integer(i + j));
428: stringTable.put(s.substring(j + 1), new Integer(i + j
429: + 1));
430: }
431:
432: writeStrI(stringTableBuf, s);
433: stringTableBuf.flush();
434: }
435:
436: }
437:
438: /**
439: * Sets the tag table for a given page.
440: * The first string in the array defines tag 5, the second tag 6 etc.
441: */
442:
443: public void setTagTable(int page, String[] tagTable) {
444: // TODO: clear entries in tagTable?
445:
446: for (int i = 0; i < tagTable.length; i++) {
447: if (tagTable[i] != null) {
448: Object idx = new int[] { page, i + 5 };
449: this .tagTable.put(tagTable[i], idx);
450: }
451: }
452: }
453:
454: /**
455: * Sets the attribute start Table for a given page.
456: * The first string in the array defines attribute
457: * 5, the second attribute 6 etc.
458: * Please use the
459: * character '=' (without quote!) as delimiter
460: * between the attribute name and the (start of the) value
461: */
462: public void setAttrStartTable(int page, String[] attrStartTable) {
463:
464: for (int i = 0; i < attrStartTable.length; i++) {
465: if (attrStartTable[i] != null) {
466: Object idx = new int[] { page, i + 5 };
467: this .attrStartTable.put(attrStartTable[i], idx);
468: }
469: }
470: }
471:
472: /**
473: * Sets the attribute value Table for a given page.
474: * The first string in the array defines attribute value 0x85,
475: * the second attribute value 0x86 etc.
476: */
477: public void setAttrValueTable(int page, String[] attrValueTable) {
478: // clear entries in this.table!
479: for (int i = 0; i < attrValueTable.length; i++) {
480: if (attrValueTable[i] != null) {
481: Object idx = new int[] { page, i + 0x085 };
482: this.attrValueTable.put(attrValueTable[i], idx);
483: }
484: }
485: }
486: }
|