View Javadoc

1   /*
2    * Copyright (c) 2008, Ounce Labs, Inc.
3    * All rights reserved.
4    *
5    * Redistribution and use in source and binary forms, with or without
6    * modification, are permitted provided that the following conditions are met:
7    *     * Redistributions of source code must retain the above copyright
8    *       notice, this list of conditions and the following disclaimer.
9    *     * Redistributions in binary form must reproduce the above copyright
10   *       notice, this list of conditions and the following disclaimer in the
11   *       documentation and/or other materials provided with the distribution.
12   *     * Neither the name of the <organization> nor the
13   *       names of its contributors may be used to endorse or promote products
14   *       derived from this software without specific prior written permission.
15   *
16   * THIS SOFTWARE IS PROVIDED BY OUNCE LABS, INC. ``AS IS'' AND ANY
17   * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18   * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19   * DISCLAIMED. IN NO EVENT SHALL OUNCE LABS, INC. BE LIABLE FOR ANY
20   * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25   * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26   */
27  package org.codehaus.mojo.ounce.core;
28  
29  import org.w3c.dom.*;
30  
31  import java.io.FileOutputStream;
32  import java.io.IOException;
33  import java.io.OutputStreamWriter;
34  import java.util.ArrayList;
35  import java.util.HashMap;
36  
37  /**
38   * An XML Writer
39   * 
40   * @author <a href="mailto:sam.headrick@ouncelabs.com">Sam Headrick</a>
41   */
42  public class XmlWriter
43  {
44  
45      public static final String START_ELEMENT = "<";
46  
47      public static final String START_CLOSE_ELEMENT = "</";
48  
49      public static final String END_CLOSE_ELEMENT = "/>";
50  
51      public static final String END_ELEMENT = ">";
52  
53      public static final String HEADER_TEXT = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
54  
55      public static String RETURN_CHAR = "\n";
56  
57      public static final String TAB_CHAR = "\t";
58  
59      public static final String QUOTE = "\"";
60  
61      public static final String EQUALS = "=";
62  
63      public static final String SPACE = " ";
64  
65      public static final String WINDOWS_FAMILY = "windows";
66  
67      public static final String LINUX_FAMILY = "linux";
68  
69      public static final String SOLARIS_FAMILY = "sunos";
70  
71      public static final String UNKNOWN_FAMILY = "other";
72  
73      public static final String[] OS_FAMILIES = new String[] { WINDOWS_FAMILY, LINUX_FAMILY, SOLARIS_FAMILY };
74  
75      private FileOutputStream m_outstream;
76  
77      private OutputStreamWriter m_writer;
78  
79      private boolean m_formatXml = true;
80  
81      private HashMap/* <String, Boolean> */m_attributesOnSameLine = new HashMap/* <String, Boolean> */();
82  
83      private HashMap/* <String, String[]> */m_attributeOrder = new HashMap/* <String, String[]> */();
84  
85      private String m_tabs = "";
86  
87      private boolean m_writeEmptyValues = false;
88  
89      private boolean m_defaultToAttributesOnSameLine = false;
90  
91      public XmlWriter()
92      {
93          this( true );
94      }
95  
96      public XmlWriter( boolean formatXml )
97      {
98          m_formatXml = formatXml;
99          if ( getOsFamily().equals( WINDOWS_FAMILY ) )
100         {
101             RETURN_CHAR = "\r\n";
102         }
103     }
104 
105     public void setDefaultToAttributesOnSameLine( boolean value )
106     {
107         m_defaultToAttributesOnSameLine = value;
108     }
109 
110     public void setAttributesOnSameLine( String nodeName, boolean attributesOnSameLine )
111     {
112         m_attributesOnSameLine.put( nodeName, new Boolean( attributesOnSameLine ) );
113     }
114 
115     public void setAttributeOrder( String nodeName, String[] attributeOrder )
116     {
117         m_attributeOrder.put( nodeName, attributeOrder );
118     }
119 
120     public void setWriteEmptyValues( boolean writeEmptyValues )
121     {
122         m_writeEmptyValues = writeEmptyValues;
123     }
124 
125     public void saveXmlFile( String filePath, Document xmlDoc )
126         throws IOException
127     {
128         if ( xmlDoc != null )
129         {
130             m_outstream = new FileOutputStream( filePath );
131             m_writer = new OutputStreamWriter( m_outstream, "UTF-8" );
132 
133             startXmlDoc();
134 
135             Element root = xmlDoc.getDocumentElement();
136 
137             writeElement( root );
138 
139             endXmlDoc();
140         }
141     }
142 
143     private void writeElement( Node element )
144         throws IOException
145     {
146 
147         if ( element.getNodeType() != Node.ELEMENT_NODE )
148         {
149             // skip
150             return;
151         }
152 
153         NamedNodeMap attributeMap = element.getAttributes();
154 
155         boolean hasChildren = false;
156 
157         NodeList childNodes = element.getChildNodes();
158         for ( int i = 0; i < childNodes.getLength(); i++ )
159         {
160             Node node = childNodes.item( i );
161             if ( node.getNodeType() == Node.ELEMENT_NODE )
162             {
163                 hasChildren = true;
164                 break;
165             }
166         }
167 
168         if ( hasChildren )
169         {
170             openElement( element.getNodeName(), attributeMap );
171 
172             for ( int i = 0; i < childNodes.getLength(); i++ )
173             {
174                 writeElement( childNodes.item( i ) );
175             }
176 
177             closeElement( element.getNodeName() );
178         }
179         else
180         {
181             // no element children
182 
183             // check for text child
184             String value = null;
185             for ( int i = 0; i < childNodes.getLength(); i++ )
186             {
187                 Node node = childNodes.item( i );
188                 if ( node.getNodeType() == Node.TEXT_NODE )
189                 {
190                     value = node.getNodeValue();
191                     break;
192                 }
193             }
194 
195             if ( value == null )
196             {
197                 addElementNoValue( element.getNodeName(), attributeMap );
198             }
199             else
200             {
201                 addElementAndValue( element.getNodeName(), value, attributeMap );
202             }
203         }
204     }
205 
206     private void addElementAndValue( String nodeName, String value, NamedNodeMap attributeMap )
207         throws IOException
208     {
209         startWriteLine();
210         writeStartElement( nodeName, attributeMap );
211         m_writer.write( value );
212         writeCloseElement( nodeName );
213         endWriteLine();
214     }
215 
216     private void addElementNoValue( String nodeName, NamedNodeMap attributeMap )
217         throws IOException
218     {
219         startWriteLine();
220         writeElementNoValue( nodeName, attributeMap );
221         endWriteLine();
222     }
223 
224     private void writeElementNoValue( String nodeName, NamedNodeMap attributeMap )
225         throws IOException
226     {
227         m_writer.write( START_ELEMENT );
228         m_writer.write( nodeName );
229         writeAttributes( nodeName, attributeMap );
230         m_writer.write( END_CLOSE_ELEMENT );
231     }
232 
233     private void openElement( String nodeName, NamedNodeMap attributeMap )
234         throws IOException
235     {
236         startWriteLine();
237         writeStartElement( nodeName, attributeMap );
238         endWriteLine();
239         incrementTabs();
240     }
241 
242     private void writeStartElement( String nodeName, NamedNodeMap attributeMap )
243         throws IOException
244     {
245         m_writer.write( START_ELEMENT );
246         m_writer.write( nodeName );
247         writeAttributes( nodeName, attributeMap );
248         m_writer.write( END_ELEMENT );
249     }
250 
251     private void writeAttributes( String nodeName, NamedNodeMap attributeMap )
252         throws IOException
253     {
254         if ( attributeMap != null )
255         {
256 
257             boolean attributesOnSameLine = false;
258 
259             if ( m_defaultToAttributesOnSameLine )
260             {
261                 attributesOnSameLine = true;
262             }
263 
264             if ( m_attributesOnSameLine.get( nodeName ) != null )
265             {
266                 attributesOnSameLine = ( (Boolean) m_attributesOnSameLine.get( nodeName ) ).booleanValue();
267             }
268 
269             if ( !attributesOnSameLine )
270             {
271                 m_writer.write( RETURN_CHAR );
272                 incrementTabs();
273             }
274 
275             ArrayList/* <Node> */attributeList = new ArrayList/* <Node> */();
276 
277             if ( m_attributeOrder.get( nodeName ) != null )
278             {
279                 // the attributes have a specific order
280                 String[] attributeOrder = (String[]) m_attributeOrder.get( nodeName );
281                 for ( int i = 0; i < attributeOrder.length; i++ )
282                 {
283                     Node node = attributeMap.getNamedItem( attributeOrder[i] );
284                     attributeList.add( node );
285                 }
286             }
287             else
288             {
289                 for ( int i = 0; i < attributeMap.getLength(); i++ )
290                 {
291                     Node node = attributeMap.item( i );
292                     attributeList.add( node );
293                 }
294             }
295 
296             for ( int i = 0; i < attributeList.size(); i++ )
297             {
298 
299                 Node node = (Node) attributeList.get( i );
300                 if ( node != null && node.getNodeValue() != null
301                     && ( node.getNodeValue().length() > 0 || m_writeEmptyValues ) )
302                 {
303 
304                     if ( !attributesOnSameLine )
305                     {
306                         m_writer.write( m_tabs );
307                     }
308 
309                     m_writer.write( SPACE );
310                     m_writer.write( node.getNodeName() );
311                     m_writer.write( EQUALS );
312                     m_writer.write( QUOTE );
313                     m_writer.write( safeEncode( node.getNodeValue() ) );
314                     m_writer.write( QUOTE );
315 
316                     if ( !attributesOnSameLine )
317                     {
318                         m_writer.write( RETURN_CHAR );
319                     }
320                 }
321             }
322 
323             if ( !attributesOnSameLine )
324             {
325                 decrementTabs();
326                 m_writer.write( m_tabs );
327             }
328         }
329     }
330 
331     private void closeElement( String nodeName )
332         throws IOException
333     {
334         decrementTabs();
335         startWriteLine();
336         writeCloseElement( nodeName );
337         endWriteLine();
338     }
339 
340     private void writeCloseElement( String nodeName )
341         throws IOException
342     {
343         m_writer.write( START_CLOSE_ELEMENT );
344         m_writer.write( nodeName );
345         m_writer.write( END_ELEMENT );
346     }
347 
348     public void startWriteLine()
349         throws IOException
350     {
351         if ( m_formatXml )
352         {
353             m_writer.write( m_tabs );
354         }
355     }
356 
357     public void endWriteLine()
358         throws IOException
359     {
360         if ( m_formatXml )
361         {
362             m_writer.write( RETURN_CHAR );
363         }
364     }
365 
366     private void startXmlDoc()
367         throws IOException
368     {
369         m_writer.write( HEADER_TEXT );
370         m_writer.write( RETURN_CHAR );
371     }
372 
373     private void endXmlDoc()
374         throws IOException
375     {
376         m_writer.flush();
377         m_writer.close();
378         m_outstream.close();
379     }
380 
381     private void incrementTabs()
382     {
383         if ( m_formatXml )
384         {
385             m_tabs += TAB_CHAR;
386         }
387     }
388 
389     private void decrementTabs()
390     {
391         if ( m_formatXml )
392         {
393             if ( m_tabs.length() >= TAB_CHAR.length() )
394             {
395                 m_tabs = m_tabs.substring( TAB_CHAR.length() );
396             }
397         }
398     }
399 
400     /**
401      * Code copied from XMLEncoder to java.bean.XMLEncoder, replaces XML special characters with
402      * 
403      * the XML special encoding. If the output writer already encodes the XML, do not use this method,
404      * 
405      * as it will be encoded twice.
406      * 
407      * @param str the string to encode
408      * @return the encoded string
409      */
410     public static String safeEncode( String str )
411     {
412         StringBuffer result = null;
413 
414         for ( int i = 0, max = str.length(), delta = 0; i < max; i++ )
415         {
416             char c = str.charAt( i );
417             String replacement = null;
418             if ( c == '&' )
419             {
420                 replacement = "&amp;";
421             }
422             else if ( c == '<' )
423             {
424                 replacement = "&lt;";
425             }
426             else if ( c == '\r' )
427             {
428                 replacement = "&#13;";
429             }
430             else if ( c == '>' )
431             {
432                 replacement = "&gt;";
433             }
434             else if ( c == '"' )
435             {
436                 replacement = "&quot;";
437             }
438             else if ( c == '\'' )
439             {
440                 replacement = "&apos;";
441             }
442 
443             if ( replacement != null )
444             {
445                 if ( result == null )
446                 {
447                     result = new StringBuffer( str );
448                 }
449 
450                 result.replace( i + delta, i + delta + 1, replacement );
451                 delta += ( replacement.length() - 1 );
452             }
453         }
454 
455         if ( result == null )
456         {
457             return str;
458         }
459 
460         return result.toString();
461     }
462 
463     /**
464      * @return the operating system name
465      */
466     private static String getOsName()
467     {
468         return System.getProperty( "os.name" ).toLowerCase();
469     }
470 
471     /**
472      * Get the OS family, this looks in the OS_FAMILIES array for a match
473      * 
474      * @return the os family
475      */
476     public static String getOsFamily()
477     {
478         String osName = getOsName();
479         String familyName = UNKNOWN_FAMILY;
480         for ( int i = 0; i < OS_FAMILIES.length; ++i )
481         {
482             if ( osName.startsWith( OS_FAMILIES[i] ) )
483             {
484                 familyName = OS_FAMILIES[i];
485                 break;
486             }
487         }
488         return familyName;
489     }
490 }