Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: org/progeeks/meta/xml/XmlPrintWriter.java


1   /*
2    * $Id: XmlPrintWriter.java,v 1.4 2003/09/22 05:45:26 pspeed Exp $
3    *
4    * Copyright (c) 2001-2002, Paul Speed
5    * All rights reserved.
6    *
7    * Redistribution and use in source and binary forms, with or without
8    * modification, are permitted provided that the following conditions
9    * are met:
10   *
11   * 1) Redistributions of source code must retain the above copyright notice,
12   *    this list of conditions and the following disclaimer.
13   * 2) Redistributions in binary form must reproduce the above copyright
14   *    notice, this list of conditions and the following disclaimer in the
15   *    documentation and/or other materials provided with the distribution.
16   * 3) Neither the names "Progeeks", "Meta-JB", nor the names of its contributors
17   *    may be used to endorse or promote products derived from this software
18   *    without specific prior written permission.
19   *
20   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
24   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30   * POSSIBILITY OF SUCH DAMAGE.
31   */
32  
33  package org.progeeks.meta.xml;
34  
35  import java.io.*;
36  import java.util.*;
37  
38  
39  /**
40   *  Extends the functionality of the IndentPrintWriter to keep
41   *  track of tag state and provide attribute support, etc..  Also,
42   *  all output is encoded as appropriate for its context.
43   *
44   *  @version   $Revision: 1.4 $
45   *  @author    Paul Speed
46   */
47  public class XmlPrintWriter extends IndentPrintWriter
48  {
49      private List tags = new LinkedList();
50  
51      private static final int CTX_BODY = 0;
52      private static final int CTX_TAG = 1;
53      private static final int CTX_COMMENT = 2;
54      private static final int CTX_DATA = 3;
55  
56      /**
57       *  Holds the current context mode to keep track of whether
58       *  or not we are in a tag, a comment, a CDATA block, or just
59       *  normal body text.
60       */
61      private int context = CTX_BODY;
62  
63      /**
64       *  The name of the currently open tag or null if there
65       *  is no open tag.
66       */
67      private String openTag = null;
68  
69      /**
70       *  True if attributes have been written for the current
71       *  tag.
72       */
73      private boolean hasAttributes = false;
74  
75      /**
76       *  Set to true if the current tag is in single tag
77       *  form, ie: <tag />  This is necessary for auto-closing
78       *  the tag.
79       */
80      private boolean singleTag = false;
81  
82  
83      public XmlPrintWriter( Writer out )
84      {
85          super( out );
86      }
87  
88      /**
89       *  Prints the tag start and sets up the indent for nesting.
90       */
91      public void pushTag( String tag )
92      {
93          closeBlock();
94  
95          tags.add( 0, tag );
96          print( "<" + tag );
97          context = CTX_TAG;
98          openTag = tag;
99      }
100 
101     /**
102      *  Prints the specified tag in single-element form, ie: <tag />
103      */
104     public void printTag( String tag )
105     {
106         closeBlock();
107         print( "<" + tag );
108         context = CTX_TAG;
109         singleTag = true;
110         openTag = tag;
111     }
112 
113     /**
114      *  Writes the specified attribute to the current tag if it is still
115      *  open.  Otherwise it will throw an IllegalStateException.  The name
116      *  will be verified for XML attribute name restrictions and the
117      *  value will be encoded as necessary for attribute values.
118      *
119      *  @param name The name of the attribute.
120      *  @param value The value to be written.  If the value is null then
121      *               the attribute will not be written.
122      */
123     public void printAttribute( String name, String value )
124     {
125         if( value == null )
126             return;
127 
128         if( context != CTX_TAG )
129             {
130             throw new IllegalStateException( "No open tags to receive attributes." );
131             }
132 
133         // Check the name
134 
135         // Encode the value
136         value = encodeXml( value, true );
137 
138         if( !hasAttributes )
139             print( " " );
140 
141         print( name + "=\"" + value + "\" " );
142 
143         hasAttributes = true;
144     }
145 
146     /**
147      *  Prints the closing element for the current tag and pops the indent.
148      */
149     public String popTag()
150     {
151         if( tags.size() == 0 )
152             throw new IllegalStateException( "Tag stack is empty." );
153         String tag = (String)tags.remove( 0 );
154 
155         // If we're still the same tag mode, then we've never written
156         // anything inside the tag and we can close the tag better
157         // as if it were a non-nested tag.
158         if( context == CTX_TAG && openTag == tag )
159             {
160             singleTag = true;
161             closeBlock();
162             }
163         else
164             {
165             closeBlock();
166 
167             popIndent();
168             println( "</" + tag + ">" );
169             }
170 
171         return( tag );
172     }
173 
174     /**
175      *  Prints the closing elements for the stacked tags until the
176      *  specified tag is reached and popped.  If the tag is never
177      *  found than an illegal state exception is thrown.
178      */
179     public void popTag( String tag )
180     {
181         while( !tag.equals( popTag() ) )
182             ;
183     }
184 
185     /**
186      *  Prints the specified string as an enclosed comment.
187      */
188     public void printComment( String comment )
189     {
190         startComment();
191         print( comment );
192         closeComment();
193     }
194 
195     /**
196      *  Starts a comment block.
197      */
198     public void startComment()
199     {
200         closeBlock();
201         print( "<!-- " );
202 
203         context = CTX_COMMENT;
204 
205         // Push an indent so that subsequent lines will line
206         // up with the first.  Five spaces are needed for that.
207         pushIndent( "     " );
208     }
209 
210     /**
211      *  Closes the comment block.
212      */
213     public void closeComment()
214     {
215         if( context != CTX_COMMENT )
216             return;
217         popIndent();
218         println( " -->" );
219 
220         context = CTX_BODY;
221     }
222 
223     /**
224      *  Starts a CDATA block.
225      */
226     public void startDataBlock()
227     {
228         closeBlock();
229         setOverrideIndent( true );
230         println( "<![CDATA[" );
231 
232         context = CTX_DATA;
233     }
234 
235     /**
236      *  Closes a CDATA block.
237      */
238     public void closeDataBlock()
239     {
240         if( context != CTX_DATA )
241             return;
242         println( "]]>" );
243         setOverrideIndent( false );
244 
245         context = CTX_BODY;
246     }
247 
248     /**
249      *  Closes the current tag or the current comment block
250      *  depending on what block is currently open.
251      */
252     protected void closeBlock()
253     {
254         switch( context )
255             {
256             default:
257             case CTX_BODY:
258                 break;
259             case CTX_TAG:
260                 closeTag();
261                 break;
262             case CTX_COMMENT:
263                 closeComment();
264                 break;
265             case CTX_DATA:
266                 closeDataBlock();
267                 break;
268             }
269     }
270 
271     /**
272      *  Closes the current tag if open.  Otherwise this is a noop.
273      *  Note this is not to be confused with popTag() which actually
274      *  writes a closing tag in many cases.
275      */
276     protected void closeTag()
277     {
278         if( context != CTX_TAG )
279             return;
280 
281         if( !singleTag )
282             println( ">" );
283         else if( hasAttributes )
284             println( "/>" );
285         else
286             println( " />" );
287 
288         // Auto-indent for standard tags
289         if( !singleTag )
290             {
291             pushIndent();
292             }
293 
294         context = CTX_BODY;
295         singleTag = false;
296         hasAttributes = false;
297         openTag = null;
298     }
299 
300     /**
301      *  Automatically finished writing any in-progess tags and then
302      *  closes the file.
303      */
304     public void close()
305     {
306         while( tags.size() > 0 )
307             popTag();
308 
309         super.close();
310     }
311 
312     /**
313      *  Overridden to provide XML encoding when appropriate.
314      */
315     public void write( int c )
316     {
317         writeEncoded( c );
318     }
319 
320     /**
321      *  Overridden to provide XML encoding when appropriate.
322      */
323     public void write( char[] buff, int off, int len )
324     {
325         switch( context )
326             {
327             case CTX_BODY:
328             case CTX_COMMENT:
329                 // Only provide low-level encoding for body and comment
330                 // text.
331                 // Gotta do it the slow way
332                 for( int i = 0; i < len; i++ )
333                     {
334                     writeEncoded( buff[ off + i ] );
335                     }
336                 return;
337             case CTX_TAG:
338             case CTX_DATA:
339             default:
340                 break;
341             }
342         super.write( buff, off, len );
343     }
344 
345     /**
346      *  Overridden to provide XML encoding when appropriate.
347      */
348     public void write( String s, int off, int len )
349     {
350         switch( context )
351             {
352             case CTX_BODY:
353             case CTX_COMMENT:
354                 // Only provide low-level encoding for body and comment
355                 // text.
356                 // Gotta do it the slow way
357                 for( int i = 0; i < len; i++ )
358                     {
359                     char c = s.charAt( off + i );
360                     writeEncoded( c );
361                     }
362                 return;
363             case CTX_TAG:
364             case CTX_DATA:
365             default:
366                 break;
367             }
368 
369         super.write( s, off, len );
370     }
371 
372     /**
373      *  Returns true if the specified character should be encoded.
374      */
375     protected boolean isEncodable( int c )
376     {
377         // Right now just the one
378         if( c == '&' )
379             return( true );
380         return( false );
381     }
382 
383     /**
384      *  Writes the specified value out as an encoded string if
385      *  necessary.
386      */
387     protected void writeEncoded( int c )
388     {
389         if( c == '&' )
390             {
391             super.write( "&amp;", 0, 5 );
392             }
393         else
394             {
395             super.write( c );
396             }
397     }
398 
399     /**
400      *  Returns an encoded safe string for the specified value.
401      *  If quoted is true then it is also safe to encode quotes
402      *  and <> signs.
403      */
404     private static String encodeXml( String value, boolean quoted )
405     {
406         value = value.replaceAll( "&", "&amp;" );
407         if( quoted )
408             {
409             value = value.replaceAll( "\"", "&quot;" );
410             value = value.replaceAll( "'", "&apos;" );
411             value = value.replaceAll( "<", "&lt;" );
412             value = value.replaceAll( ">", "&gt;" );
413             }
414         return( value );
415     }
416 
417     public static void main( String[] args ) throws IOException
418     {
419         OutputStreamWriter osw = new OutputStreamWriter( System.out );
420         XmlPrintWriter out = new XmlPrintWriter( osw );
421 
422         out.pushTag( "root" );
423 
424         out.startComment();
425         out.println( "This is a test of a comment block." );
426         out.println( "This is the second line of the test." );
427         out.println( "This line requires encoding the & symbol." );
428 
429         out.pushTag( "firstInner" );
430         out.printAttribute( "test", "A test attribute." );
431         out.printTag( "single" );
432         out.printTag( "anotherSingle" );
433         out.printAttribute( "foo", "Bar" );
434         out.printAttribute( "bar", "Foo" );
435 
436         out.popTag();
437 
438         out.println( "This is just some body text floating around in the" );
439         out.println( "middle of nowhere.  And it has an & symbol too." );
440 
441         out.startDataBlock();
442         out.println( "This is just some raw data that is not XML at all." );
443         out.println( "This is how you would include raw data in an XML file." );
444 
445         out.pushTag( "secondInner" );
446         out.pushTag( "nestedInner" );
447 
448         out.popTag( "secondInner" );
449 
450         out.startComment();
451         out.print( "We should be in the root tag now." );
452         out.closeComment();
453 
454         out.close();
455     }
456 }
457 
458