001: // Copyright (c) 2001, 2003, 2005, 2006 Per M.A. Bothner and Brainfood Inc.
002: // This is free software; for terms and warranty disclaimer see ./COPYING.
003:
004: package gnu.xml;
005:
006: import gnu.lists.*;
007: import java.io.*;
008: import gnu.text.*;
009: import gnu.math.RealNum;
010: import gnu.text.PrettyWriter;
011: import gnu.mapping.OutPort;
012: import gnu.mapping.ThreadLocation;
013: import gnu.mapping.Symbol;
014: import java.math.BigDecimal;
015: import gnu.expr.Keyword;
016: import gnu.kawa.xml.XmlNamespace;
017:
018: /** Print an event stream in XML format on a PrintWriter. */
019:
020: public class XMLPrinter extends OutPort implements PositionConsumer,
021: XConsumer {
022: /** Controls whether to add extra indentation.
023: * -1: don't add indentation; 0: pretty-print (avoid needless newlines);
024: * 1: indent (force). */
025: public int printIndent = -1;
026: /** When indentating, should attributes be lined up? */
027: public boolean indentAttributes;
028:
029: boolean printXMLdecl = false;
030:
031: public void setPrintXMLdecl(boolean value) {
032: printXMLdecl = value;
033: }
034:
035: boolean inDocument;
036: boolean inAttribute = false;
037: boolean inStartTag = false;
038: /** 0: not in comment; 1: in comment normal; 2: in comment after '-'. */
039: int inComment;
040: boolean needXMLdecl = false;
041: boolean canonicalize = true;
042: public boolean canonicalizeCDATA;
043: /** Handling of empty elements.
044: * 0: No element element tags, as required for canonical XML:
045: * {@code <br></br>}.
046: * 1: Use XML-style empty element tags: {@code <br/>}
047: * 2: Use HTML-compatible empty element tags: {@code <br />}
048: */
049: public int useEmptyElementTag = 2;
050: public boolean escapeText = true;
051: public boolean escapeNonAscii = true;
052: boolean isHtml = false;
053: boolean undeclareNamespaces = false;
054: Object style;
055: /** Fluid parameter to control whether a DOCTYPE declaration is emitted.
056: * If non-null, this is the the public identifier. */
057: public static final ThreadLocation doctypeSystem = new ThreadLocation(
058: "doctype-system");
059: /** The system identifier emitted in a DOCTYPE declaration.
060: * Has no effect if doctypeSystem returns null.
061: * If non-null, this is the the system identifier. */
062: public static final ThreadLocation doctypePublic = new ThreadLocation(
063: "doctype-public");
064: public static final ThreadLocation indentLoc = new ThreadLocation(
065: "xml-indent");
066:
067: public boolean strict;
068:
069: /** Chain of currently active namespace nodes. */
070: NamespaceBinding namespaceBindings = NamespaceBinding.predefinedXML;
071:
072: /** Stack of namespaceBindings as of active startElement calls. */
073: NamespaceBinding[] namespaceSaveStack = new NamespaceBinding[20];
074:
075: Object[] elementNameStack = new Object[20];
076:
077: /** Difference between number of startElement and endElement calls so far. */
078: int elementNesting;
079:
080: /* If prev==WORD, last output was a number or similar. */
081: private static final int WORD = -2;
082: private static final int ELEMENT_START = -3;
083: private static final int ELEMENT_END = -4;
084: private static final int COMMENT = -5;
085: private static final int KEYWORD = -6;
086: int prev = ' ';
087:
088: char savedHighSurrogate; // should perhaps be combined with prev?
089:
090: public XMLPrinter(OutPort out, boolean autoFlush) {
091: super (out, autoFlush);
092: }
093:
094: public XMLPrinter(Writer out, boolean autoFlush) {
095: super (out, autoFlush);
096: }
097:
098: public XMLPrinter(OutputStream out, boolean autoFlush) {
099: super (new OutputStreamWriter(out), true, autoFlush);
100: }
101:
102: public XMLPrinter(Writer out) {
103: super (out);
104: }
105:
106: public XMLPrinter(OutputStream out) {
107: super (new OutputStreamWriter(out), false, false);
108: }
109:
110: public XMLPrinter(OutputStream out, Path path) {
111: super (new OutputStreamWriter(out), true, false, path);
112: }
113:
114: public static XMLPrinter make(OutPort out, Object style) {
115: XMLPrinter xout = new XMLPrinter(out, true);
116: xout.setStyle(style);
117: return xout;
118: }
119:
120: /** Convert argument to string in XML syntax. */
121:
122: public static String toString(Object value) {
123: StringWriter stringWriter = new StringWriter();
124: new XMLPrinter(stringWriter).writeObject(value);
125: return stringWriter.toString();
126: }
127:
128: public void setStyle(Object style) {
129: this .style = style;
130: useEmptyElementTag = canonicalize ? 0 : 1;
131: if ("html".equals(style)) {
132: isHtml = true;
133: useEmptyElementTag = 2;
134: // Pre-establish the html namespace, so it doesn't get printed.
135: if (namespaceBindings == NamespaceBinding.predefinedXML)
136: namespaceBindings = XmlNamespace.HTML_BINDINGS;
137: } else if (namespaceBindings == XmlNamespace.HTML_BINDINGS)
138: namespaceBindings = NamespaceBinding.predefinedXML;
139: if ("xhtml".equals(style))
140: useEmptyElementTag = 2;
141: if ("plain".equals(style))
142: escapeText = false;
143: }
144:
145: boolean mustHexEscape(int v) {
146: return (v >= 127 && (v <= 159 || escapeNonAscii))
147: || v == 0x2028
148: // We must escape control characters in attributes,
149: // since otherwise they get normalized to ' '.
150: || (v < ' ' && (inAttribute || (v != '\t' && v != '\n')));
151: }
152:
153: public void write(int v) {
154: closeTag();
155: if (printIndent >= 0) {
156: if ((v == '\r' || v == '\n')) {
157: if (v != '\n' || prev != '\r')
158: writeBreak(PrettyWriter.NEWLINE_MANDATORY);
159: if (inComment > 0)
160: inComment = 1;
161: return;
162: }
163: }
164: if (!escapeText) {
165: bout.write(v);
166: prev = v;
167: } else if (inComment > 0) {
168: if (v == '-') {
169: if (inComment == 1)
170: inComment = 2;
171: else
172: bout.write(' ');
173: } else
174: inComment = 1;
175: super .write(v);
176: } else {
177: prev = ';';
178: if (v == '<' && !(isHtml && inAttribute))
179: bout.write("<");
180: else if (v == '>')
181: bout.write(">");
182: else if (v == '&')
183: bout.write("&");
184: else if (v == '\"' && inAttribute)
185: bout.write(""");
186: else if (mustHexEscape(v)) {
187: int i = v;
188: if (v >= 0xD800) {
189: if (v < 0xDC00) {
190: savedHighSurrogate = (char) v;
191: return;
192: } else if (v < 0xE000) { // low surrogate
193: //if (highSurrogate < 0xDC00 || highSurrogate > 0xE000)
194: // error();
195: i = (savedHighSurrogate - 0xD800) * 0x400
196: + (i - 0xDC00) + 0x10000;
197: savedHighSurrogate = 0;
198: }
199: }
200: bout.write("&#x" + Integer.toHexString(i).toUpperCase()
201: + ";");
202: } else {
203: bout.write(v);
204: prev = v;
205: }
206: }
207: }
208:
209: private void startWord() {
210: closeTag();
211: writeWordStart();
212: }
213:
214: public void writeBoolean(boolean v) {
215: startWord();
216: super .print(v);
217: writeWordEnd();
218: }
219:
220: protected void startNumber() {
221: startWord();
222: }
223:
224: protected void endNumber() {
225: writeWordEnd();
226: }
227:
228: public void closeTag() {
229: if (inStartTag && !inAttribute) {
230: if (printIndent >= 0 && indentAttributes)
231: endLogicalBlock("");
232: bout.write('>');
233: inStartTag = false;
234: prev = ELEMENT_START;
235: } else if (needXMLdecl) {
236: // should also include encoding declaration FIXME.
237: bout.write("<?xml version=\"1.0\"?>\n");
238: if (printIndent >= 0) {
239: startLogicalBlock("", "", 2);
240: }
241: needXMLdecl = false;
242: }
243: }
244:
245: void setIndentMode() {
246: Object xmlIndent = indentLoc.get(null);
247: String indent = xmlIndent == null ? null : xmlIndent.toString();
248: if (indent == null)
249: printIndent = -1;
250: else if (indent.equals("pretty"))
251: printIndent = 0;
252: else if (indent.equals("always") || indent.equals("yes"))
253: printIndent = 1;
254: else
255: // if (ident.equals("no")) or default:
256: printIndent = -1;
257: }
258:
259: public void startDocument() {
260: if (printXMLdecl) {
261: // We should emit an XML declaration, but don't emit it yet, in case
262: // we get it later as a processing instruction.
263: needXMLdecl = true;
264: }
265: setIndentMode();
266: inDocument = true;
267: if (printIndent >= 0 && !needXMLdecl)
268: startLogicalBlock("", "", 2);
269: }
270:
271: public void endDocument() {
272: inDocument = false;
273: if (printIndent >= 0)
274: endLogicalBlock("");
275: freshLine();
276: }
277:
278: public void beginEntity(Object base) {
279: }
280:
281: public void endEntity() {
282: }
283:
284: protected void writeQName(Object name) {
285: if (name instanceof Symbol) {
286: Symbol sname = (Symbol) name;
287: String prefix = sname.getPrefix();
288: if (prefix != null && prefix.length() > 0) {
289: bout.write(prefix);
290: bout.write(':');
291: }
292: bout.write(sname.getLocalPart());
293: } else
294: bout.write(name == null ? "{null name}" : (String) name);
295: }
296:
297: public void startElement(Object type) {
298: closeTag();
299: if (elementNesting == 0) {
300: if (!inDocument)
301: setIndentMode();
302: Object systemIdentifier = doctypeSystem.get(null);
303: if (systemIdentifier != null) {
304: String systemId = systemIdentifier.toString();
305: if (systemId.length() > 0) {
306: Object publicIdentifier = doctypePublic.get(null);
307: bout.write("<!DOCTYPE ");
308: bout.write(type.toString());
309: String publicId = publicIdentifier == null ? null
310: : publicIdentifier.toString();
311: if (publicId != null && publicId.length() > 0) {
312: bout.write(" PUBLIC \"");
313: bout.write(publicId);
314: bout.write("\" \"");
315: } else {
316: bout.write(" SYSTEM \"");
317: }
318: bout.write(systemId);
319: bout.write("\">");
320: println();
321: }
322: }
323: }
324: if (printIndent >= 0) {
325: if (prev == ELEMENT_START || prev == ELEMENT_END
326: || prev == COMMENT)
327: writeBreak(printIndent > 0 ? PrettyWriter.NEWLINE_MANDATORY
328: : PrettyWriter.NEWLINE_LINEAR);
329: startLogicalBlock("", "", 2);
330: }
331: bout.write('<');
332: writeQName(type);
333: if (printIndent >= 0 && indentAttributes)
334: startLogicalBlock("", "", 2);
335: elementNameStack[elementNesting] = type;
336: NamespaceBinding elementBindings = null;
337: namespaceSaveStack[elementNesting++] = namespaceBindings;
338: if (type instanceof XName) {
339: elementBindings = ((XName) type).namespaceNodes;
340: NamespaceBinding join = NamespaceBinding.commonAncestor(
341: elementBindings, namespaceBindings);
342: int numBindings = elementBindings == null ? 0
343: : elementBindings.count(join);
344: NamespaceBinding[] sortedBindings = new NamespaceBinding[numBindings];
345: int i = 0;
346: boolean sortNamespaces = canonicalize;
347: check_namespaces: for (NamespaceBinding ns = elementBindings; ns != join; ns = ns.next) {
348: int j = i;
349: boolean skip = false;
350: String uri = ns.getUri();
351: String prefix = ns.getPrefix();
352: while (--j >= 0) {
353: NamespaceBinding ns_j = sortedBindings[j];
354: // If (compare(ns, ns_j) <= 0) break:
355: String prefix_j = ns_j.getPrefix();
356: if (prefix == prefix_j)
357: continue check_namespaces;
358: // If we're not canonicalizing, we just want to suppress
359: // duplicates, rather than putting them in order.
360: // Note we put the bindings in reverse order, since that's
361: // the following print loop expects.
362: if (!sortNamespaces)
363: continue;
364: if (prefix == null)
365: break;
366: if (prefix_j != null
367: && prefix.compareTo(prefix_j) <= 0)
368: break;
369: sortedBindings[j + 1] = ns_j;
370: }
371: if (sortNamespaces)
372: j++;
373: else
374: j = i;
375: sortedBindings[j] = ns;
376: i++;
377: }
378: numBindings = i;
379: // Note we print the bindings in reverse order, since the chain
380: // is in reverse document order.
381: for (i = numBindings; --i >= 0;) {
382: NamespaceBinding ns = sortedBindings[i];
383: String prefix = ns.prefix;
384: String uri = ns.uri;
385: if (uri == namespaceBindings.resolve(prefix))
386: // A matching namespace declaration is already in scope.
387: continue;
388: if (uri == null && prefix != null
389: && !undeclareNamespaces)
390: continue;
391: bout.write(' '); // prettyprint break
392: if (prefix == null)
393: bout.write("xmlns");
394: else {
395: bout.write("xmlns:");
396: bout.write(prefix);
397: }
398: bout.write("=\"");
399: inAttribute = true;
400: if (uri != null)
401: write(uri);
402: inAttribute = false;
403: bout.write('\"');
404: }
405: if (undeclareNamespaces) {
406: // As needed emit namespace undeclarations as in
407: // the XML Namespaces 1.1 Candidate Recommendation.
408: // Most commonly this loop will run zero times.
409: for (NamespaceBinding ns = namespaceBindings; ns != join; ns = ns.next) {
410: String prefix = ns.prefix;
411: if (ns.uri != null
412: && elementBindings.resolve(prefix) == null) {
413: bout.write(' '); // prettyprint break
414: if (prefix == null)
415: bout.write("xmlns");
416: else {
417: bout.write("xmlns:");
418: bout.write(prefix);
419: }
420: bout.write("=\"\"");
421: }
422: }
423: }
424: namespaceBindings = elementBindings;
425: }
426: if (elementNesting >= namespaceSaveStack.length) {
427: NamespaceBinding[] nstmp = new NamespaceBinding[2 * elementNesting];
428: System.arraycopy(namespaceSaveStack, 0, nstmp, 0,
429: elementNesting);
430: namespaceSaveStack = nstmp;
431: Object[] nmtmp = new Object[2 * elementNesting];
432: System.arraycopy(elementNameStack, 0, nmtmp, 0,
433: elementNesting);
434: elementNameStack = nmtmp;
435: }
436:
437: inStartTag = true;
438: if (isHtml) {
439: String typeName = (type instanceof Symbol ? ((Symbol) type)
440: .getLocalPart() : type.toString());
441: if ("script".equals(typeName) || "style".equals(typeName))
442: escapeText = false;
443: }
444: }
445:
446: static final String HtmlEmptyTags = "/area/base/basefont/br/col/frame/hr/img/input/isindex/link/meta/para/";
447:
448: public static boolean isHtmlEmptyElementTag(String name) {
449: int index = HtmlEmptyTags.indexOf(name);
450: return index > 0 && HtmlEmptyTags.charAt(index - 1) == '/'
451: && HtmlEmptyTags.charAt(index + name.length()) == '/';
452: }
453:
454: public void endElement() {
455: if (useEmptyElementTag == 0)
456: closeTag();
457: Object type = elementNameStack[elementNesting - 1];
458: String typeName = !isHtml ? null // not needed
459: : type instanceof Symbol ? ((Symbol) type)
460: .getLocalPart() : type.toString();
461: if (inStartTag) {
462: if (printIndent >= 0 && indentAttributes) {
463: endLogicalBlock("");
464: }
465: bout.write(isHtml ? (isHtmlEmptyElementTag(typeName) ? ">"
466: : "></" + typeName + ">")
467: : (useEmptyElementTag == 2 ? " />" : "/>"));
468: inStartTag = false;
469: } else {
470: if (printIndent >= 0) {
471: setIndentation(0, false);
472: if (prev == ELEMENT_END)
473: writeBreak(printIndent > 0 ? PrettyWriter.NEWLINE_MANDATORY
474: : PrettyWriter.NEWLINE_LINEAR);
475: }
476: bout.write("</");
477: writeQName(type);
478: bout.write(">");
479: }
480: if (printIndent >= 0) {
481: endLogicalBlock("");
482: }
483: prev = ELEMENT_END;
484: if (isHtml
485: && !escapeText
486: && ("script".equals(typeName) || "style"
487: .equals(typeName)))
488: escapeText = true;
489:
490: namespaceBindings = namespaceSaveStack[--elementNesting];
491: namespaceSaveStack[elementNesting] = null;
492: elementNameStack[elementNesting] = null;
493: }
494:
495: /** Write a attribute for the current element.
496: * This is only allowed immediately after a startElement. */
497: public void startAttribute(Object attrType) {
498: if (!inStartTag && strict)
499: error("attribute not in element", "SENR0001");
500: if (inAttribute)
501: bout.write('"');
502: inAttribute = true;
503: bout.write(' ');
504: if (printIndent >= 0)
505: writeBreakFill();
506: bout.write(attrType.toString()); // FIXME: use Symbol.print
507: bout.write("=\"");
508: prev = ' ';
509: }
510:
511: public void endAttribute() {
512: if (inAttribute) {
513: if (prev != KEYWORD) {
514: bout.write('"');
515: inAttribute = false;
516: }
517: prev = ' ';
518: }
519: }
520:
521: public void writeDouble(double d) {
522: startWord();
523: bout.write(formatDouble(d));
524: }
525:
526: public void writeFloat(float f) {
527: startWord();
528: bout.write(formatFloat(f));
529: }
530:
531: /** Helper to format xs:double according to XPath/XQuery specification. */
532: public static String formatDouble(double d) {
533: if (Double.isNaN(d))
534: return "NaN";
535: boolean neg = d < 0;
536: if (Double.isInfinite(d))
537: return neg ? "-INF" : "INF";
538: double dabs = neg ? -d : d;
539: String dstr = Double.toString(d);
540: // Unfortunately, XQuery's rules for when to use scientific notation
541: // are different from Java's. So fixup the string, if needed.
542: if ((dabs >= 1000000 || dabs < 0.000001) && dabs != 0.0)
543: return RealNum.toStringScientific(dstr);
544: else
545: return formatDecimal(RealNum.toStringDecimal(dstr));
546: }
547:
548: /** Helper to format xs:float according to XPath/XQuery specification. */
549: public static String formatFloat(float f) {
550: if (Float.isNaN(f))
551: return "NaN";
552: boolean neg = f < 0;
553: if (Float.isInfinite(f))
554: return neg ? "-INF" : "INF";
555: float fabs = neg ? -f : f;
556: String fstr = Float.toString(f);
557: // Unfortunately, XQuery's rules for when to use scientific notation
558: // are different from Java's. So fixup the string, if needed.
559: if ((fabs >= 1000000 || fabs < 0.000001) && fabs != 0.0)
560: return RealNum.toStringScientific(fstr);
561: else
562: return formatDecimal(RealNum.toStringDecimal(fstr));
563: }
564:
565: /** Format java.math.BigDecimal as needed for XPath/XQuery's xs:decimal.
566: * Specifically this means removing trailing fractional zeros, and a trailing
567: * decimal point. However, note that the XML Schema canonical representation
568: * does require a decimal point and at least one fractional digit.
569: */
570: public static String formatDecimal(BigDecimal dec) {
571: /* #ifdef JAVA5 */
572: // return formatDecimal(dec.toPlainString());
573: /* #else */
574: return formatDecimal(dec.toString());
575: /* #endif */
576: }
577:
578: static String formatDecimal(String str) {
579: int dot = str.indexOf('.');
580: if (dot >= 0) {
581: int len = str.length();
582: for (int pos = len;;) {
583: char ch = str.charAt(--pos);
584: if (ch != '0') {
585: if (ch != '.')
586: pos++;
587: return pos == len ? str : str.substring(0, pos);
588: }
589: }
590: }
591: return str;
592: }
593:
594: public void print(Object v) {
595: if (v instanceof BigDecimal)
596: v = formatDecimal((BigDecimal) v);
597: else if (v instanceof Double || v instanceof gnu.math.DFloNum)
598: v = formatDouble(((Number) v).doubleValue());
599: else if (v instanceof Float)
600: v = formatFloat(((Float) v).floatValue());
601: write(v == null ? "(null)" : v.toString());
602: }
603:
604: public void writeObject(Object v) {
605: if (v instanceof SeqPosition) {
606: bout.clearWordEnd();
607: SeqPosition pos = (SeqPosition) v;
608: pos.sequence.consumeNext(pos.ipos, this );
609: if (pos.sequence instanceof NodeTree)
610: prev = '-';
611: return;
612: }
613: if (v instanceof Consumable && !(v instanceof UnescapedData)) {
614: ((Consumable) v).consume(this );
615: return;
616: }
617: if (v instanceof Keyword) {
618: startAttribute(((Keyword) v).getName());
619: prev = KEYWORD;
620: return;
621: }
622: closeTag();
623: if (v instanceof UnescapedData) {
624: bout.clearWordEnd();
625: bout.write(((UnescapedData) v).getData());
626: prev = '-';
627: } else if (v instanceof Char)
628: Char.print(((Char) v).intValue(), this );
629: else {
630: startWord();
631: prev = ' ';
632: print(v);
633: writeWordEnd();
634: prev = WORD;
635: }
636: }
637:
638: /** Write each element of a sequence, which can be an array,
639: * a Sequence or a Consumable. */
640: //public void writeAll(Object sequence);
641: /** True if consumer is ignoring rest of element.
642: * The producer can use this information to skip ahead. */
643: public boolean ignoring() {
644: return false;
645: }
646:
647: public void write(String str, int start, int length) {
648: if (length > 0) {
649: closeTag();
650: int limit = start + length;
651: int count = 0;
652: while (start < limit) {
653: char c = str.charAt(start++);
654: if (mustHexEscape(c)
655: || (inComment > 0 ? (c == '-' || inComment == 2)
656: : (c == '<' || c == '>' || c == '&' || (inAttribute && (c == '"' || c < ' '))))) {
657: if (count > 0)
658: bout.write(str, start - 1 - count, count);
659: write(c);
660: count = 0;
661: } else
662: count++;
663: }
664: if (count > 0)
665: bout.write(str, limit - count, count);
666: }
667: prev = '-';
668: }
669:
670: public void write(char[] buf, int off, int len) {
671: if (len > 0) {
672: closeTag();
673: int limit = off + len;
674: int count = 0;
675: while (off < limit) {
676: char c = buf[off++];
677: if (mustHexEscape(c)
678: || (inComment > 0 ? (c == '-' || inComment == 2)
679: : (c == '<' || c == '>' || c == '&' || (inAttribute && (c == '"' || c < ' '))))) {
680: if (count > 0)
681: bout.write(buf, off - 1 - count, count);
682: write(c);
683: count = 0;
684: } else
685: count++;
686: }
687: if (count > 0)
688: bout.write(buf, limit - count, count);
689: }
690: prev = '-';
691: }
692:
693: public void writePosition(AbstractSequence seq, int ipos) {
694: seq.consumeNext(ipos, this );
695: }
696:
697: public void writeBaseUri(Object uri) {
698: }
699:
700: public void beginComment() {
701: closeTag();
702: if (printIndent >= 0) {
703: if (prev == ELEMENT_START || prev == ELEMENT_END
704: || prev == COMMENT)
705: writeBreak(printIndent > 0 ? PrettyWriter.NEWLINE_MANDATORY
706: : PrettyWriter.NEWLINE_LINEAR);
707: }
708: bout.write("<!--");
709: inComment = 1;
710: }
711:
712: public void endComment() {
713: bout.write("-->");
714: prev = COMMENT;
715: inComment = 0;
716: }
717:
718: public void writeComment(String chars) {
719: beginComment();
720: write(chars);
721: endComment();
722: }
723:
724: public void writeComment(char[] chars, int offset, int length) {
725: beginComment();
726: write(chars, offset, length);
727: endComment();
728: }
729:
730: public void writeCDATA(char[] chars, int offset, int length) {
731: if (canonicalizeCDATA) {
732: write(chars, offset, length);
733: return;
734: }
735: closeTag();
736: bout.write("<![CDATA[");
737: int limit = offset + length;
738: // Look for and deal with embedded "]]>". This can't happen with
739: // data generated from XML, but maybe somebody generated invalid CDATA.
740: for (int i = offset; i < limit - 2; i++) {
741: if (chars[i] == ']' && chars[i + 1] == ']'
742: && chars[i + 2] == '>') {
743: if (i > offset)
744: bout.write(chars, offset, i - offset);
745: print("]]]><![CDATA[]>");
746: offset = i + 3;
747: length = limit - offset;
748: i = i + 2;
749: }
750: }
751: bout.write(chars, offset, length);
752: bout.write("]]>");
753: prev = '>';
754: }
755:
756: public void writeProcessingInstruction(String target,
757: char[] content, int offset, int length) {
758: if ("xml".equals(target))
759: needXMLdecl = false;
760: closeTag();
761: bout.write("<?");
762: print(target);
763: print(' ');
764: bout.write(content, offset, length);
765: bout.write("?>");
766: prev = '>';
767: }
768:
769: public void consume(SeqPosition position) {
770: position.sequence.consumeNext(position.ipos, this );
771: }
772:
773: public void error(String msg, String code) {
774: throw new RuntimeException("serialization error: " + msg + " ["
775: + code + ']');
776: }
777: }
|