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