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: package org.kxml2.io;
022:
023: import java.io.*;
024: import org.xmlpull.v1.*;
025:
026: public class KXmlSerializer implements XmlSerializer {
027:
028: // static final String UNDEFINED = ":";
029:
030: private Writer writer;
031:
032: private boolean pending;
033: private int auto;
034: private int depth;
035:
036: private String[] elementStack = new String[12];
037: //nsp/prefix/name
038: private int[] nspCounts = new int[4];
039: private String[] nspStack = new String[8];
040: //prefix/nsp; both empty are ""
041: private boolean[] indent = new boolean[4];
042: private boolean unicode;
043: private String encoding;
044:
045: private final void check(boolean close) throws IOException {
046: if (!pending)
047: return;
048:
049: depth++;
050: pending = false;
051:
052: if (indent.length <= depth) {
053: boolean[] hlp = new boolean[depth + 4];
054: System.arraycopy(indent, 0, hlp, 0, depth);
055: indent = hlp;
056: }
057: indent[depth] = indent[depth - 1];
058:
059: for (int i = nspCounts[depth - 1]; i < nspCounts[depth]; i++) {
060: writer.write(' ');
061: writer.write("xmlns");
062: if (!"".equals(nspStack[i * 2])) {
063: writer.write(':');
064: writer.write(nspStack[i * 2]);
065: } else if ("".equals(getNamespace())
066: && !"".equals(nspStack[i * 2 + 1]))
067: throw new IllegalStateException(
068: "Cannot set default namespace for elements in no namespace");
069: writer.write("=\"");
070: writeEscaped(nspStack[i * 2 + 1], '"');
071: writer.write('"');
072: }
073:
074: if (nspCounts.length <= depth + 1) {
075: int[] hlp = new int[depth + 8];
076: System.arraycopy(nspCounts, 0, hlp, 0, depth + 1);
077: nspCounts = hlp;
078: }
079:
080: nspCounts[depth + 1] = nspCounts[depth];
081: // nspCounts[depth + 2] = nspCounts[depth];
082:
083: writer.write(close ? " />" : ">");
084: }
085:
086: private final void writeEscaped(String s, int quot)
087: throws IOException {
088:
089: for (int i = 0; i < s.length(); i++) {
090: char c = s.charAt(i);
091: switch (c) {
092: case '\n':
093: case '\r':
094: case '\t':
095: if (quot == -1)
096: writer.write(c);
097: else
098: writer.write("&#" + ((int) c) + ';');
099: break;
100: case '&':
101: writer.write("&");
102: break;
103: case '>':
104: writer.write(">");
105: break;
106: case '<':
107: writer.write("<");
108: break;
109: case '"':
110: case '\'':
111: if (c == quot) {
112: writer.write(c == '"' ? """ : "'");
113: break;
114: }
115: default:
116: //if(c < ' ')
117: // throw new IllegalArgumentException("Illegal control code:"+((int) c));
118:
119: if (c >= ' ' && c != '@' && (c < 127 || unicode))
120: writer.write(c);
121: else
122: writer.write("&#" + ((int) c) + ";");
123:
124: }
125: }
126: }
127:
128: /*
129: private final void writeIndent() throws IOException {
130: writer.write("\r\n");
131: for (int i = 0; i < depth; i++)
132: writer.write(' ');
133: }*/
134:
135: public void docdecl(String dd) throws IOException {
136: writer.write("<!DOCTYPE");
137: writer.write(dd);
138: writer.write(">");
139: }
140:
141: public void endDocument() throws IOException {
142: while (depth > 0) {
143: endTag(elementStack[depth * 3 - 3],
144: elementStack[depth * 3 - 1]);
145: }
146: flush();
147: }
148:
149: public void entityRef(String name) throws IOException {
150: check(false);
151: writer.write('&');
152: writer.write(name);
153: writer.write(';');
154: }
155:
156: public boolean getFeature(String name) {
157: //return false;
158: return ("http://xmlpull.org/v1/doc/features.html#indent-output"
159: .equals(name)) ? indent[depth] : false;
160: }
161:
162: public String getPrefix(String namespace, boolean create) {
163: try {
164: return getPrefix(namespace, false, create);
165: } catch (IOException e) {
166: throw new RuntimeException(e.toString());
167: }
168: }
169:
170: private final String getPrefix(String namespace,
171: boolean includeDefault, boolean create) throws IOException {
172:
173: for (int i = nspCounts[depth + 1] * 2 - 2; i >= 0; i -= 2) {
174: if (nspStack[i + 1].equals(namespace)
175: && (includeDefault || !nspStack[i].equals(""))) {
176: String cand = nspStack[i];
177: for (int j = i + 2; j < nspCounts[depth + 1] * 2; j++) {
178: if (nspStack[j].equals(cand)) {
179: cand = null;
180: break;
181: }
182: }
183: if (cand != null)
184: return cand;
185: }
186: }
187:
188: if (!create)
189: return null;
190:
191: String prefix;
192:
193: if ("".equals(namespace))
194: prefix = "";
195: else {
196: do {
197: prefix = "n" + (auto++);
198: for (int i = nspCounts[depth + 1] * 2 - 2; i >= 0; i -= 2) {
199: if (prefix.equals(nspStack[i])) {
200: prefix = null;
201: break;
202: }
203: }
204: } while (prefix == null);
205: }
206:
207: boolean p = pending;
208: pending = false;
209: setPrefix(prefix, namespace);
210: pending = p;
211: return prefix;
212: }
213:
214: public Object getProperty(String name) {
215: throw new RuntimeException("Unsupported property");
216: }
217:
218: public void ignorableWhitespace(String s) throws IOException {
219: text(s);
220: }
221:
222: public void setFeature(String name, boolean value) {
223: if ("http://xmlpull.org/v1/doc/features.html#indent-output"
224: .equals(name)) {
225: indent[depth] = value;
226: } else
227: throw new RuntimeException("Unsupported Feature");
228: }
229:
230: public void setProperty(String name, Object value) {
231: throw new RuntimeException("Unsupported Property:" + value);
232: }
233:
234: public void setPrefix(String prefix, String namespace)
235: throws IOException {
236:
237: check(false);
238: if (prefix == null)
239: prefix = "";
240: if (namespace == null)
241: namespace = "";
242:
243: String defined = getPrefix(namespace, true, false);
244:
245: // boil out if already defined
246:
247: if (prefix.equals(defined))
248: return;
249:
250: int pos = (nspCounts[depth + 1]++) << 1;
251:
252: if (nspStack.length < pos + 1) {
253: String[] hlp = new String[nspStack.length + 16];
254: System.arraycopy(nspStack, 0, hlp, 0, pos);
255: nspStack = hlp;
256: }
257:
258: nspStack[pos++] = prefix;
259: nspStack[pos] = namespace;
260: }
261:
262: public void setOutput(Writer writer) {
263: this .writer = writer;
264:
265: // elementStack = new String[12]; //nsp/prefix/name
266: //nspCounts = new int[4];
267: //nspStack = new String[8]; //prefix/nsp
268: //indent = new boolean[4];
269:
270: nspCounts[0] = 2;
271: nspCounts[1] = 2;
272: nspStack[0] = "";
273: nspStack[1] = "";
274: nspStack[2] = "xml";
275: nspStack[3] = "http://www.w3.org/XML/1998/namespace";
276: pending = false;
277: auto = 0;
278: depth = 0;
279:
280: unicode = false;
281: }
282:
283: public void setOutput(OutputStream os, String encoding)
284: throws IOException {
285: if (os == null)
286: throw new IllegalArgumentException();
287: setOutput(encoding == null ? new OutputStreamWriter(os)
288: : new OutputStreamWriter(os, encoding));
289: this .encoding = encoding;
290: if (encoding != null
291: && encoding.toLowerCase().startsWith("utf"))
292: unicode = true;
293: }
294:
295: public void startDocument(String encoding, Boolean standalone)
296: throws IOException {
297: writer.write("<?xml version='1.0' ");
298:
299: if (encoding != null) {
300: this .encoding = encoding;
301: if (encoding.toLowerCase().startsWith("utf"))
302: unicode = true;
303: }
304:
305: if (this .encoding != null) {
306: writer.write("encoding='");
307: writer.write(this .encoding);
308: writer.write("' ");
309: }
310:
311: if (standalone != null) {
312: writer.write("standalone='");
313: writer.write(standalone.booleanValue() ? "yes" : "no");
314: writer.write("' ");
315: }
316: writer.write("?>");
317: }
318:
319: public XmlSerializer startTag(String namespace, String name)
320: throws IOException {
321: check(false);
322:
323: // if (namespace == null)
324: // namespace = "";
325:
326: if (indent[depth]) {
327: writer.write("\r\n");
328: for (int i = 0; i < depth; i++)
329: writer.write(" ");
330: }
331:
332: int esp = depth * 3;
333:
334: if (elementStack.length < esp + 3) {
335: String[] hlp = new String[elementStack.length + 12];
336: System.arraycopy(elementStack, 0, hlp, 0, esp);
337: elementStack = hlp;
338: }
339:
340: String prefix = namespace == null ? "" : getPrefix(namespace,
341: true, true);
342:
343: if ("".equals(namespace)) {
344: for (int i = nspCounts[depth]; i < nspCounts[depth + 1]; i++) {
345: if ("".equals(nspStack[i * 2])
346: && !"".equals(nspStack[i * 2 + 1])) {
347: throw new IllegalStateException(
348: "Cannot set default namespace for elements in no namespace");
349: }
350: }
351: }
352:
353: elementStack[esp++] = namespace;
354: elementStack[esp++] = prefix;
355: elementStack[esp] = name;
356:
357: writer.write('<');
358: if (!"".equals(prefix)) {
359: writer.write(prefix);
360: writer.write(':');
361: }
362:
363: writer.write(name);
364:
365: pending = true;
366:
367: return this ;
368: }
369:
370: public XmlSerializer attribute(String namespace, String name,
371: String value) throws IOException {
372: if (!pending)
373: throw new IllegalStateException(
374: "illegal position for attribute");
375:
376: // int cnt = nspCounts[depth];
377:
378: if (namespace == null)
379: namespace = "";
380:
381: // depth--;
382: // pending = false;
383:
384: String prefix = "".equals(namespace) ? "" : getPrefix(
385: namespace, false, true);
386:
387: // pending = true;
388: // depth++;
389:
390: /* if (cnt != nspCounts[depth]) {
391: writer.write(' ');
392: writer.write("xmlns");
393: if (nspStack[cnt * 2] != null) {
394: writer.write(':');
395: writer.write(nspStack[cnt * 2]);
396: }
397: writer.write("=\"");
398: writeEscaped(nspStack[cnt * 2 + 1], '"');
399: writer.write('"');
400: }
401: */
402:
403: writer.write(' ');
404: if (!"".equals(prefix)) {
405: writer.write(prefix);
406: writer.write(':');
407: }
408: writer.write(name);
409: writer.write('=');
410: char q = value.indexOf('"') == -1 ? '"' : '\'';
411: writer.write(q);
412: writeEscaped(value, q);
413: writer.write(q);
414:
415: return this ;
416: }
417:
418: public void flush() throws IOException {
419: check(false);
420: writer.flush();
421: }
422:
423: /*
424: public void close() throws IOException {
425: check();
426: writer.close();
427: }
428: */
429: public XmlSerializer endTag(String namespace, String name)
430: throws IOException {
431:
432: if (!pending)
433: depth--;
434: // if (namespace == null)
435: // namespace = "";
436:
437: if ((namespace == null && elementStack[depth * 3] != null)
438: || (namespace != null && !namespace
439: .equals(elementStack[depth * 3]))
440: || !elementStack[depth * 3 + 2].equals(name))
441: throw new IllegalArgumentException("</{" + namespace + "}"
442: + name + "> does not match start");
443:
444: if (pending) {
445: check(true);
446: depth--;
447: } else {
448: if (indent[depth + 1]) {
449: writer.write("\r\n");
450: for (int i = 0; i < depth; i++)
451: writer.write(" ");
452: }
453:
454: writer.write("</");
455: String prefix = elementStack[depth * 3 + 1];
456: if (!"".equals(prefix)) {
457: writer.write(prefix);
458: writer.write(':');
459: }
460: writer.write(name);
461: writer.write('>');
462: }
463:
464: nspCounts[depth + 1] = nspCounts[depth];
465: return this ;
466: }
467:
468: public String getNamespace() {
469: return getDepth() == 0 ? null
470: : elementStack[getDepth() * 3 - 3];
471: }
472:
473: public String getName() {
474: return getDepth() == 0 ? null
475: : elementStack[getDepth() * 3 - 1];
476: }
477:
478: public int getDepth() {
479: return pending ? depth + 1 : depth;
480: }
481:
482: public XmlSerializer text(String text) throws IOException {
483: check(false);
484: indent[depth] = false;
485: writeEscaped(text, -1);
486: return this ;
487: }
488:
489: public XmlSerializer text(char[] text, int start, int len)
490: throws IOException {
491: text(new String(text, start, len));
492: return this ;
493: }
494:
495: public void cdsect(String data) throws IOException {
496: check(false);
497: writer.write("<![CDATA[");
498: writer.write(data);
499: writer.write("]]>");
500: }
501:
502: public void comment(String comment) throws IOException {
503: check(false);
504: writer.write("<!--");
505: writer.write(comment);
506: writer.write("-->");
507: }
508:
509: public void processingInstruction(String pi) throws IOException {
510: check(false);
511: writer.write("<?");
512: writer.write(pi);
513: writer.write("?>");
514: }
515: }
|