1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29 public class MXSerializer implements XmlSerializer {
30 protected static final String XML_URI = "http://www.w3.org/XML/1998/namespace";
31
32 protected static final String XMLNS_URI = "http://www.w3.org/2000/xmlns/";
33
34 private static final boolean TRACE_SIZING = false;
35
36 protected final String FEATURE_SERIALIZER_ATTVALUE_USE_APOSTROPHE =
37 "http://xmlpull.org/v1/doc/features.html#serializer-attvalue-use-apostrophe";
38
39 protected final String FEATURE_NAMES_INTERNED = "http://xmlpull.org/v1/doc/features.html#names-interned";
40
41 protected final String PROPERTY_SERIALIZER_INDENTATION =
42 "http://xmlpull.org/v1/doc/properties.html#serializer-indentation";
43
44 protected final String PROPERTY_SERIALIZER_LINE_SEPARATOR =
45 "http://xmlpull.org/v1/doc/properties.html#serializer-line-separator";
46
47 protected static final String PROPERTY_LOCATION = "http://xmlpull.org/v1/doc/properties.html#location";
48
49
50 protected boolean namesInterned;
51
52 protected boolean attributeUseApostrophe;
53
54 protected String indentationString = null;
55
56 protected String lineSeparator = "\n";
57
58 protected String location;
59
60 protected Writer out;
61
62 protected int autoDeclaredPrefixes;
63
64 protected int depth = 0;
65
66
67 protected String elNamespace[] = new String[2];
68
69 protected String elName[] = new String[elNamespace.length];
70
71 protected int elNamespaceCount[] = new int[elNamespace.length];
72
73
74 protected int namespaceEnd = 0;
75
76 protected String namespacePrefix[] = new String[8];
77
78 protected String namespaceUri[] = new String[namespacePrefix.length];
79
80 protected boolean finished;
81
82 protected boolean pastRoot;
83
84 protected boolean setPrefixCalled;
85
86 protected boolean startTagIncomplete;
87
88 protected boolean doIndent;
89
90 protected boolean seenTag;
91
92 protected boolean seenBracket;
93
94 protected boolean seenBracketBracket;
95
96
97 private static final int BUF_LEN = Runtime.getRuntime().freeMemory() > 1000000L ? 8 * 1024 : 256;
98
99 protected char buf[] = new char[BUF_LEN];
100
101 protected static final String precomputedPrefixes[];
102
103 static {
104 precomputedPrefixes = new String[32];
105 for (int i = 0; i < precomputedPrefixes.length; i++) {
106 precomputedPrefixes[i] = ("n" + i).intern();
107 }
108 }
109
110 private boolean checkNamesInterned = false;
111
112 private void checkInterning(String name) {
113 if (namesInterned && name != name.intern()) {
114 throw new IllegalArgumentException(
115 "all names passed as arguments must be interned" + "when NAMES INTERNED feature is enabled");
116 }
117 }
118
119 protected void reset() {
120 location = null;
121 out = null;
122 autoDeclaredPrefixes = 0;
123 depth = 0;
124
125
126 for (int i = 0; i < elNamespaceCount.length; i++) {
127 elName[i] = null;
128 elNamespace[i] = null;
129 elNamespaceCount[i] = 2;
130 }
131
132 namespaceEnd = 0;
133
134
135
136
137
138
139
140
141
142 namespacePrefix[namespaceEnd] = "xmlns";
143 namespaceUri[namespaceEnd] = XMLNS_URI;
144 ++namespaceEnd;
145
146 namespacePrefix[namespaceEnd] = "xml";
147 namespaceUri[namespaceEnd] = XML_URI;
148 ++namespaceEnd;
149
150 finished = false;
151 pastRoot = false;
152 setPrefixCalled = false;
153 startTagIncomplete = false;
154
155 seenTag = false;
156
157 seenBracket = false;
158 seenBracketBracket = false;
159 }
160
161 protected void ensureElementsCapacity() {
162 final int elStackSize = elName.length;
163
164
165 final int newSize = (depth >= 7 ? 2 * depth : 8) + 2;
166 if (TRACE_SIZING) {
167 System.err.println(getClass().getName() + " elStackSize " + elStackSize + " ==> " + newSize);
168 }
169 final boolean needsCopying = elStackSize > 0;
170 String[] arr = null;
171
172 arr = new String[newSize];
173 if (needsCopying) System.arraycopy(elName, 0, arr, 0, elStackSize);
174 elName = arr;
175 arr = new String[newSize];
176 if (needsCopying) System.arraycopy(elNamespace, 0, arr, 0, elStackSize);
177 elNamespace = arr;
178
179 final int[] iarr = new int[newSize];
180 if (needsCopying) {
181 System.arraycopy(elNamespaceCount, 0, iarr, 0, elStackSize);
182 } else {
183
184 iarr[0] = 0;
185 }
186 elNamespaceCount = iarr;
187 }
188
189 protected void ensureNamespacesCapacity() {
190
191
192
193
194
195 final int newSize = namespaceEnd > 7 ? 2 * namespaceEnd : 8;
196 if (TRACE_SIZING) {
197 System.err.println(getClass().getName() + " namespaceSize " + namespacePrefix.length + " ==> " + newSize);
198 }
199 final String[] newNamespacePrefix = new String[newSize];
200 final String[] newNamespaceUri = new String[newSize];
201 if (namespacePrefix != null) {
202 System.arraycopy(namespacePrefix, 0, newNamespacePrefix, 0, namespaceEnd);
203 System.arraycopy(namespaceUri, 0, newNamespaceUri, 0, namespaceEnd);
204 }
205 namespacePrefix = newNamespacePrefix;
206 namespaceUri = newNamespaceUri;
207
208
209
210
211
212
213
214
215
216
217
218
219
220 }
221
222 @Override
223 public void setFeature(String name, boolean state) throws IllegalArgumentException, IllegalStateException {
224 if (name == null) {
225 throw new IllegalArgumentException("feature name can not be null");
226 }
227 if (FEATURE_NAMES_INTERNED.equals(name)) {
228 namesInterned = state;
229 } else if (FEATURE_SERIALIZER_ATTVALUE_USE_APOSTROPHE.equals(name)) {
230 attributeUseApostrophe = state;
231 } else {
232 throw new IllegalStateException("unsupported feature " + name);
233 }
234 }
235
236 @Override
237 public boolean getFeature(String name) throws IllegalArgumentException {
238 if (name == null) {
239 throw new IllegalArgumentException("feature name can not be null");
240 }
241 if (FEATURE_NAMES_INTERNED.equals(name)) {
242 return namesInterned;
243 } else if (FEATURE_SERIALIZER_ATTVALUE_USE_APOSTROPHE.equals(name)) {
244 return attributeUseApostrophe;
245 } else {
246 return false;
247 }
248 }
249
250
251 protected int offsetNewLine;
252
253 protected int indentationJump;
254
255 protected char[] indentationBuf;
256
257 protected int maxIndentLevel;
258
259 protected boolean writeLineSeparator;
260
261 protected boolean writeIndentation;
262
263
264
265
266
267 protected void rebuildIndentationBuf() {
268 if (doIndent == false) return;
269 final int maxIndent = 65;
270 int bufSize = 0;
271 offsetNewLine = 0;
272 if (writeLineSeparator) {
273 offsetNewLine = lineSeparator.length();
274 bufSize += offsetNewLine;
275 }
276 maxIndentLevel = 0;
277 if (writeIndentation) {
278 indentationJump = indentationString.length();
279 maxIndentLevel = maxIndent / indentationJump;
280 bufSize += maxIndentLevel * indentationJump;
281 }
282 if (indentationBuf == null || indentationBuf.length < bufSize) {
283 indentationBuf = new char[bufSize + 8];
284 }
285 int bufPos = 0;
286 if (writeLineSeparator) {
287 for (int i = 0; i < lineSeparator.length(); i++) {
288 indentationBuf[bufPos++] = lineSeparator.charAt(i);
289 }
290 }
291 if (writeIndentation) {
292 for (int i = 0; i < maxIndentLevel; i++) {
293 for (int j = 0; j < indentationString.length(); j++) {
294 indentationBuf[bufPos++] = indentationString.charAt(j);
295 }
296 }
297 }
298 }
299
300
301 protected void writeIndent() throws IOException {
302 final int start = writeLineSeparator ? 0 : offsetNewLine;
303 final int level = (depth > maxIndentLevel) ? maxIndentLevel : depth;
304 out.write(indentationBuf, start, (level * indentationJump) + offsetNewLine);
305 }
306
307 @Override
308 public void setProperty(String name, Object value) throws IllegalArgumentException, IllegalStateException {
309 if (name == null) {
310 throw new IllegalArgumentException("property name can not be null");
311 }
312 if (PROPERTY_SERIALIZER_INDENTATION.equals(name)) {
313 indentationString = (String) value;
314 } else if (PROPERTY_SERIALIZER_LINE_SEPARATOR.equals(name)) {
315 lineSeparator = (String) value;
316 } else if (PROPERTY_LOCATION.equals(name)) {
317 location = (String) value;
318 } else {
319 throw new IllegalStateException("unsupported property " + name);
320 }
321 writeLineSeparator = lineSeparator != null && lineSeparator.length() > 0;
322 writeIndentation = indentationString != null && indentationString.length() > 0;
323
324 doIndent = indentationString != null && (writeLineSeparator || writeIndentation);
325
326
327 rebuildIndentationBuf();
328 seenTag = false;
329 }
330
331 @Override
332 public Object getProperty(String name) throws IllegalArgumentException {
333 if (name == null) {
334 throw new IllegalArgumentException("property name can not be null");
335 }
336 if (PROPERTY_SERIALIZER_INDENTATION.equals(name)) {
337 return indentationString;
338 } else if (PROPERTY_SERIALIZER_LINE_SEPARATOR.equals(name)) {
339 return lineSeparator;
340 } else if (PROPERTY_LOCATION.equals(name)) {
341 return location;
342 } else {
343 return null;
344 }
345 }
346
347 private String getLocation() {
348 return location != null ? " @" + location : "";
349 }
350
351
352 public Writer getWriter() {
353 return out;
354 }
355
356 @Override
357 public void setOutput(Writer writer) {
358 reset();
359 out = writer;
360 }
361
362 @Override
363 public void setOutput(OutputStream os, String encoding) throws IOException {
364 if (os == null) throw new IllegalArgumentException("output stream can not be null");
365 reset();
366 if (encoding != null) {
367 out = new OutputStreamWriter(os, encoding);
368 } else {
369 out = new OutputStreamWriter(os);
370 }
371 }
372
373 @Override
374 public void startDocument(String encoding, Boolean standalone) throws IOException {
375 char apos = attributeUseApostrophe ? '\'' : '"';
376 if (attributeUseApostrophe) {
377 out.write("<?xml version='1.0'");
378 } else {
379 out.write("<?xml version=\"1.0\"");
380 }
381 if (encoding != null) {
382 out.write(" encoding=");
383 out.write(apos);
384 out.write(encoding);
385 out.write(apos);
386
387 }
388 if (standalone != null) {
389 out.write(" standalone=");
390 out.write(apos);
391 if (standalone) {
392 out.write("yes");
393 } else {
394 out.write("no");
395 }
396 out.write(apos);
397
398
399
400
401
402 }
403 out.write("?>");
404 if (writeLineSeparator) {
405 out.write(lineSeparator);
406 }
407 }
408
409 @Override
410 public void endDocument() throws IOException {
411
412 while (depth > 0) {
413 endTag(elNamespace[depth], elName[depth]);
414 }
415 if (writeLineSeparator) {
416 out.write(lineSeparator);
417 }
418
419
420 finished = pastRoot = startTagIncomplete = true;
421 out.flush();
422 }
423
424 @Override
425 public void setPrefix(String prefix, String namespace) throws IOException {
426 if (startTagIncomplete) closeStartTag();
427
428
429 if (prefix == null) {
430 prefix = "";
431 }
432 if (!namesInterned) {
433 prefix = prefix.intern();
434 } else if (checkNamesInterned) {
435 checkInterning(prefix);
436 } else if (prefix == null) {
437 throw new IllegalArgumentException("prefix must be not null" + getLocation());
438 }
439
440
441 for (int i = elNamespaceCount[depth]; i < namespaceEnd; i++) {
442 if (prefix == namespacePrefix[i]) {
443 throw new IllegalStateException("duplicated prefix " + printable(prefix) + getLocation());
444 }
445 }
446
447 if (!namesInterned) {
448 namespace = namespace.intern();
449 } else if (checkNamesInterned) {
450 checkInterning(namespace);
451 } else if (namespace == null) {
452 throw new IllegalArgumentException("namespace must be not null" + getLocation());
453 }
454
455 if (namespaceEnd >= namespacePrefix.length) {
456 ensureNamespacesCapacity();
457 }
458 namespacePrefix[namespaceEnd] = prefix;
459 namespaceUri[namespaceEnd] = namespace;
460 ++namespaceEnd;
461 setPrefixCalled = true;
462 }
463
464 protected String lookupOrDeclarePrefix(String namespace) {
465 return getPrefix(namespace, true);
466 }
467
468 @Override
469 public String getPrefix(String namespace, boolean generatePrefix) {
470
471 if (!namesInterned) {
472
473 namespace = namespace.intern();
474 } else if (checkNamesInterned) {
475 checkInterning(namespace);
476
477 }
478 if (namespace == null) {
479 throw new IllegalArgumentException("namespace must be not null" + getLocation());
480 } else if (namespace.length() == 0) {
481 throw new IllegalArgumentException("default namespace cannot have prefix" + getLocation());
482 }
483
484
485 for (int i = namespaceEnd - 1; i >= 0; --i) {
486 if (namespace == namespaceUri[i]) {
487 final String prefix = namespacePrefix[i];
488
489 for (int p = namespaceEnd - 1; p > i; --p) {
490 if (prefix == namespacePrefix[p])
491 continue;
492 }
493 return prefix;
494 }
495 }
496
497
498 if (!generatePrefix) {
499 return null;
500 }
501 return generatePrefix(namespace);
502 }
503
504 private String generatePrefix(String namespace) {
505
506 while (true) {
507 ++autoDeclaredPrefixes;
508
509 final String prefix = autoDeclaredPrefixes < precomputedPrefixes.length
510 ? precomputedPrefixes[autoDeclaredPrefixes]
511 : ("n" + autoDeclaredPrefixes).intern();
512
513 for (int i = namespaceEnd - 1; i >= 0; --i) {
514 if (prefix == namespacePrefix[i]) {
515 continue;
516 }
517 }
518
519
520 if (namespaceEnd >= namespacePrefix.length) {
521 ensureNamespacesCapacity();
522 }
523 namespacePrefix[namespaceEnd] = prefix;
524 namespaceUri[namespaceEnd] = namespace;
525 ++namespaceEnd;
526
527 return prefix;
528 }
529 }
530
531 @Override
532 public int getDepth() {
533 return depth;
534 }
535
536 @Override
537 public String getNamespace() {
538 return elNamespace[depth];
539 }
540
541 @Override
542 public String getName() {
543 return elName[depth];
544 }
545
546 @Override
547 public XmlSerializer startTag(String namespace, String name) throws IOException {
548
549 if (startTagIncomplete) {
550 closeStartTag();
551 }
552 seenBracket = seenBracketBracket = false;
553 if (doIndent && depth > 0 && seenTag) {
554 writeIndent();
555 }
556 seenTag = true;
557 setPrefixCalled = false;
558 startTagIncomplete = true;
559 ++depth;
560 if ((depth + 1) >= elName.length) {
561 ensureElementsCapacity();
562 }
563
564
565 if (checkNamesInterned && namesInterned) checkInterning(namespace);
566 elNamespace[depth] = (namesInterned || namespace == null) ? namespace : namespace.intern();
567
568
569 if (checkNamesInterned && namesInterned) checkInterning(name);
570 elName[depth] = (namesInterned || name == null) ? name : name.intern();
571 if (out == null) {
572 throw new IllegalStateException("setOutput() must called set before serialization can start");
573 }
574 out.write('<');
575 if (namespace != null) {
576
577 if (namespace.length() > 0) {
578
579 String prefix = null;
580 if (depth > 0 && (namespaceEnd - elNamespaceCount[depth - 1]) == 1) {
581
582
583 String uri = namespaceUri[namespaceEnd - 1];
584 if (uri == namespace || uri.equals(namespace)) {
585 String elPfx = namespacePrefix[namespaceEnd - 1];
586
587 for (int pos = elNamespaceCount[depth - 1] - 1; pos >= 2; --pos) {
588 String pf = namespacePrefix[pos];
589 if (pf == elPfx || pf.equals(elPfx)) {
590 String n = namespaceUri[pos];
591 if (n == uri || n.equals(uri)) {
592 --namespaceEnd;
593 prefix = elPfx;
594 }
595 break;
596 }
597 }
598 }
599 }
600 if (prefix == null) {
601 prefix = lookupOrDeclarePrefix(namespace);
602 }
603
604
605 if (prefix.length() > 0) {
606 out.write(prefix);
607 out.write(':');
608 }
609 } else {
610
611 for (int i = namespaceEnd - 1; i >= 0; --i) {
612 if (namespacePrefix[i] == "") {
613 final String uri = namespaceUri[i];
614 if (uri == null) {
615
616 setPrefix("", "");
617 } else if (uri.length() > 0) {
618 throw new IllegalStateException("start tag can not be written in empty default namespace "
619 + "as default namespace is currently bound to '" + uri + "'" + getLocation());
620 }
621 break;
622 }
623 }
624 }
625 }
626 out.write(name);
627 return this;
628 }
629
630 @Override
631 public XmlSerializer attribute(String namespace, String name, String value) throws IOException {
632 if (!startTagIncomplete) {
633 throw new IllegalArgumentException("startTag() must be called before attribute()" + getLocation());
634 }
635
636 out.write(' ');
637
638 if (namespace != null && namespace.length() > 0) {
639
640 if (!namesInterned) {
641 namespace = namespace.intern();
642 } else if (checkNamesInterned) {
643 checkInterning(namespace);
644 }
645 String prefix = lookupOrDeclarePrefix(namespace);
646
647 if (prefix.length() == 0) {
648
649
650 prefix = generatePrefix(namespace);
651 }
652 out.write(prefix);
653 out.write(':');
654
655
656
657
658 }
659
660 out.write(name);
661 out.write('=');
662
663 out.write(attributeUseApostrophe ? '\'' : '"');
664 writeAttributeValue(value, out);
665 out.write(attributeUseApostrophe ? '\'' : '"');
666 return this;
667 }
668
669 protected void closeStartTag() throws IOException {
670 if (finished) {
671 throw new IllegalArgumentException("trying to write past already finished output" + getLocation());
672 }
673 if (seenBracket) {
674 seenBracket = seenBracketBracket = false;
675 }
676 if (startTagIncomplete || setPrefixCalled) {
677 if (setPrefixCalled) {
678 throw new IllegalArgumentException(
679 "startTag() must be called immediately after setPrefix()" + getLocation());
680 }
681 if (!startTagIncomplete) {
682 throw new IllegalArgumentException("trying to close start tag that is not opened" + getLocation());
683 }
684
685
686 writeNamespaceDeclarations();
687 out.write('>');
688 elNamespaceCount[depth] = namespaceEnd;
689 startTagIncomplete = false;
690 }
691 }
692
693 private void writeNamespaceDeclarations() throws IOException {
694
695 for (int i = elNamespaceCount[depth - 1]; i < namespaceEnd; i++) {
696 if (doIndent && namespaceUri[i].length() > 40) {
697 writeIndent();
698 out.write(" ");
699 }
700 if (namespacePrefix[i] != "") {
701 out.write(" xmlns:");
702 out.write(namespacePrefix[i]);
703 out.write('=');
704 } else {
705 out.write(" xmlns=");
706 }
707 out.write(attributeUseApostrophe ? '\'' : '"');
708
709
710 writeAttributeValue(namespaceUri[i], out);
711
712 out.write(attributeUseApostrophe ? '\'' : '"');
713 }
714 }
715
716 @Override
717 public XmlSerializer endTag(String namespace, String name) throws IOException {
718
719
720
721
722
723 seenBracket = seenBracketBracket = false;
724 if (namespace != null) {
725 if (!namesInterned) {
726 namespace = namespace.intern();
727 } else if (checkNamesInterned) {
728 checkInterning(namespace);
729 }
730 }
731
732 if (namespace != elNamespace[depth]) {
733 throw new IllegalArgumentException("expected namespace " + printable(elNamespace[depth]) + " and not "
734 + printable(namespace) + getLocation());
735 }
736 if (name == null) {
737 throw new IllegalArgumentException("end tag name can not be null" + getLocation());
738 }
739 if (checkNamesInterned && namesInterned) {
740 checkInterning(name);
741 }
742
743 if ((!namesInterned && !name.equals(elName[depth])) || (namesInterned && name != elName[depth])) {
744 throw new IllegalArgumentException("expected element name " + printable(elName[depth]) + " and not "
745 + printable(name) + getLocation());
746 }
747 if (startTagIncomplete) {
748 writeNamespaceDeclarations();
749 out.write(" />");
750 --depth;
751 } else {
752 --depth;
753
754 if (doIndent && seenTag) {
755 writeIndent();
756 }
757 out.write("</");
758 if (namespace != null && namespace.length() > 0) {
759
760 final String prefix = lookupOrDeclarePrefix(namespace);
761
762 if (prefix.length() > 0) {
763 out.write(prefix);
764 out.write(':');
765 }
766 }
767 out.write(name);
768 out.write('>');
769 }
770 namespaceEnd = elNamespaceCount[depth];
771 startTagIncomplete = false;
772 seenTag = true;
773 return this;
774 }
775
776 @Override
777 public XmlSerializer text(String text) throws IOException {
778
779 if (startTagIncomplete || setPrefixCalled) closeStartTag();
780 if (doIndent && seenTag) seenTag = false;
781 writeElementContent(text, out);
782 return this;
783 }
784
785 @Override
786 public XmlSerializer text(char[] buf, int start, int len) throws IOException {
787 if (startTagIncomplete || setPrefixCalled) closeStartTag();
788 if (doIndent && seenTag) seenTag = false;
789 writeElementContent(buf, start, len, out);
790 return this;
791 }
792
793 @Override
794 public void cdsect(String text) throws IOException {
795 if (startTagIncomplete || setPrefixCalled || seenBracket) closeStartTag();
796 if (doIndent && seenTag) seenTag = false;
797 out.write("<![CDATA[");
798 out.write(text);
799 out.write("]]>");
800 }
801
802 @Override
803 public void entityRef(String text) throws IOException {
804 if (startTagIncomplete || setPrefixCalled || seenBracket) closeStartTag();
805 if (doIndent && seenTag) seenTag = false;
806 out.write('&');
807 out.write(text);
808 out.write(';');
809 }
810
811 @Override
812 public void processingInstruction(String text) throws IOException {
813 if (startTagIncomplete || setPrefixCalled || seenBracket) closeStartTag();
814 if (doIndent && seenTag) seenTag = false;
815 out.write("<?");
816 out.write(text);
817 out.write("?>");
818 }
819
820 @Override
821 public void comment(String text) throws IOException {
822 if (startTagIncomplete || setPrefixCalled || seenBracket) closeStartTag();
823 if (doIndent && seenTag) seenTag = false;
824 out.write("<!--");
825 out.write(text);
826 out.write("-->");
827 }
828
829 @Override
830 public void docdecl(String text) throws IOException {
831 if (startTagIncomplete || setPrefixCalled || seenBracket) closeStartTag();
832 if (doIndent && seenTag) seenTag = false;
833 out.write("<!DOCTYPE ");
834 out.write(text);
835 out.write(">");
836 }
837
838 @Override
839 public void ignorableWhitespace(String text) throws IOException {
840 if (startTagIncomplete || setPrefixCalled || seenBracket) closeStartTag();
841 if (doIndent && seenTag) seenTag = false;
842 if (text.length() == 0) {
843 throw new IllegalArgumentException("empty string is not allowed for ignorable whitespace" + getLocation());
844 }
845 out.write(text);
846 }
847
848 @Override
849 public void flush() throws IOException {
850 if (!finished && startTagIncomplete) closeStartTag();
851 out.flush();
852 }
853
854
855
856 protected void writeAttributeValue(String value, Writer out) throws IOException {
857 if (value == null) {
858 return;
859 }
860
861 final char quot = attributeUseApostrophe ? '\'' : '"';
862 final String quotEntity = attributeUseApostrophe ? "'" : """;
863
864 int pos = 0;
865 for (int i = 0; i < value.length(); i++) {
866 char ch = value.charAt(i);
867 if (ch == '&') {
868 if (i > pos) out.write(value.substring(pos, i));
869 out.write("&");
870 pos = i + 1;
871 }
872 if (ch == '<') {
873 if (i > pos) out.write(value.substring(pos, i));
874 out.write("<");
875 pos = i + 1;
876 } else if (ch == quot) {
877 if (i > pos) out.write(value.substring(pos, i));
878 out.write(quotEntity);
879 pos = i + 1;
880 } else if (ch < 32) {
881
882
883 if (ch == 13 || ch == 10 || ch == 9) {
884 if (i > pos) out.write(value.substring(pos, i));
885 out.write("&#");
886 out.write(Integer.toString(ch));
887 out.write(';');
888 pos = i + 1;
889 } else {
890 throw new IllegalStateException(
891 "character " + Integer.toString(ch) + " is not allowed in output" + getLocation());
892
893
894
895
896
897
898
899
900
901
902
903 }
904 }
905 }
906 if (pos > 0) {
907 out.write(value.substring(pos));
908 } else {
909 out.write(value);
910 }
911 }
912
913 protected void writeElementContent(String text, Writer out) throws IOException {
914 if (text == null) {
915 return;
916 }
917
918 int pos = 0;
919 for (int i = 0; i < text.length(); i++) {
920
921 char ch = text.charAt(i);
922 if (ch == ']') {
923 if (seenBracket) {
924 seenBracketBracket = true;
925 } else {
926 seenBracket = true;
927 }
928 } else {
929 if (ch == '&') {
930 if (i > pos) out.write(text.substring(pos, i));
931 out.write("&");
932 pos = i + 1;
933 } else if (ch == '<') {
934 if (i > pos) out.write(text.substring(pos, i));
935 out.write("<");
936 pos = i + 1;
937 } else if (seenBracketBracket && ch == '>') {
938 if (i > pos) out.write(text.substring(pos, i));
939 out.write(">");
940 pos = i + 1;
941 } else if (ch < 32) {
942
943 if (ch == 9 || ch == 10 || ch == 13) {
944
945
946
947
948
949
950
951
952 } else {
953
954 if (i > pos) out.write(text.substring(pos, i));
955 pos = i + 1;
956 }
957 }
958 if (seenBracket) {
959 seenBracketBracket = seenBracket = false;
960 }
961 }
962 }
963 if (pos > 0) {
964 out.write(text.substring(pos));
965 } else {
966 out.write(text);
967 }
968 }
969
970 protected void writeElementContent(char[] buf, int off, int len, Writer out) throws IOException {
971
972 final int end = off + len;
973 int pos = off;
974 for (int i = off; i < end; i++) {
975 final char ch = buf[i];
976 if (ch == ']') {
977 if (seenBracket) {
978 seenBracketBracket = true;
979 } else {
980 seenBracket = true;
981 }
982 } else {
983 if (ch == '&') {
984 if (i > pos) {
985 out.write(buf, pos, i - pos);
986 }
987 out.write("&");
988 pos = i + 1;
989 } else if (ch == '<') {
990 if (i > pos) {
991 out.write(buf, pos, i - pos);
992 }
993 out.write("<");
994 pos = i + 1;
995
996 } else if (seenBracketBracket && ch == '>') {
997 if (i > pos) {
998 out.write(buf, pos, i - pos);
999 }
1000 out.write(">");
1001 pos = i + 1;
1002 } else if (ch < 32) {
1003
1004 if (ch == 9 || ch == 10 || ch == 13) {
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015 } else {
1016 throw new IllegalStateException(
1017 "character " + Integer.toString(ch) + " is not allowed in output" + getLocation());
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029 }
1030 }
1031 if (seenBracket) {
1032 seenBracketBracket = seenBracket = false;
1033 }
1034
1035 }
1036 }
1037 if (end > pos) {
1038 out.write(buf, pos, end - pos);
1039 }
1040 }
1041
1042
1043 protected static final String printable(String s) {
1044 if (s == null) return "null";
1045 StringBuilder retval = new StringBuilder(s.length() + 16);
1046 retval.append("'");
1047 char ch;
1048 for (int i = 0; i < s.length(); i++) {
1049 addPrintable(retval, s.charAt(i));
1050 }
1051 retval.append("'");
1052 return retval.toString();
1053 }
1054
1055 protected static final String printable(char ch) {
1056 StringBuilder retval = new StringBuilder();
1057 addPrintable(retval, ch);
1058 return retval.toString();
1059 }
1060
1061 private static void addPrintable(StringBuilder retval, char ch) {
1062 switch (ch) {
1063 case '\b':
1064 retval.append("\\b");
1065 break;
1066 case '\t':
1067 retval.append("\\t");
1068 break;
1069 case '\n':
1070 retval.append("\\n");
1071 break;
1072 case '\f':
1073 retval.append("\\f");
1074 break;
1075 case '\r':
1076 retval.append("\\r");
1077 break;
1078 case '\"':
1079 retval.append("\\\"");
1080 break;
1081 case '\'':
1082 retval.append("\\\'");
1083 break;
1084 case '\\':
1085 retval.append("\\\\");
1086 break;
1087 default:
1088 if (ch < 0x20 || ch > 0x7e) {
1089 final String ss = "0000" + Integer.toString(ch, 16);
1090 retval.append("\\u").append(ss, ss.length() - 4, ss.length());
1091 } else {
1092 retval.append(ch);
1093 }
1094 }
1095 }
1096 }