View Javadoc

1   package org.apache.maven.shared.utils.xml;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import org.apache.maven.shared.utils.io.IOUtil;
23  import org.apache.maven.shared.utils.xml.pull.XmlPullParserException;
24  
25  import org.xml.sax.*;
26  import org.xml.sax.helpers.DefaultHandler;
27  
28  import javax.annotation.Nonnull;
29  import javax.annotation.WillClose;
30  
31  import java.io.*;
32  import java.util.ArrayList;
33  import java.util.List;
34  
35  /**
36   * @author Kristian Rosenvold
37   */
38  public class Xpp3DomBuilder
39  {
40      private static final boolean DEFAULT_TRIM = true;
41  
42      public static Xpp3Dom build( @WillClose @Nonnull Reader reader )
43          throws XmlPullParserException
44      {
45          return build( reader, DEFAULT_TRIM );
46      }
47  
48      public static Xpp3Dom build( @WillClose InputStream is, @Nonnull String encoding )
49          throws XmlPullParserException
50      {
51          return build( is, encoding, DEFAULT_TRIM );
52      }
53  
54      public static Xpp3Dom build( @WillClose InputStream is, @Nonnull String encoding, boolean trim )
55          throws XmlPullParserException
56      {
57          try
58          {
59              Reader reader = new InputStreamReader( is, encoding );
60              return build( reader, trim );
61          }
62          catch ( UnsupportedEncodingException e )
63          {
64              throw new RuntimeException( e );
65          }
66      }
67  
68      public static Xpp3Dom build( @WillClose Reader reader, boolean trim )
69          throws XmlPullParserException
70      {
71          try
72          {
73              DocHandler docHandler = parseSax( new InputSource( reader ), trim );
74              return docHandler.result;
75          }
76          finally
77          {
78              IOUtil.close( reader );
79          }
80      }
81  
82      private static DocHandler parseSax( @Nonnull InputSource inputSource, boolean trim )
83          throws XmlPullParserException
84      {
85          try
86          {
87              DocHandler ch = new DocHandler( trim );
88              XMLReader parser = createXmlReader();
89              parser.setContentHandler( ch );
90              parser.parse( inputSource );
91              return ch;
92          }
93          catch ( IOException e )
94          {
95              throw new XmlPullParserException( e );
96          }
97          catch ( SAXException e )
98          {
99              throw new XmlPullParserException( e );
100         }
101     }
102 
103 
104     private static XMLReader createXmlReader()
105         throws SAXException
106     {
107         XMLReader comSunXmlReader = instantiate( "com.sun.org.apache.xerces.internal.parsers.SAXParser" );
108         if ( comSunXmlReader != null )
109         {
110             return comSunXmlReader;
111         }
112 
113         String key = "org.xml.sax.driver";
114         String oldParser = System.getProperty( key );
115         System.clearProperty( key ); // There's a "slight" problem with this an parallel maven: It does not work ;)
116 
117         try
118         {
119             return org.xml.sax.helpers.XMLReaderFactory.createXMLReader();
120         }
121         finally
122         {
123             if ( oldParser != null )
124             {
125                 System.setProperty( key, oldParser );
126             }
127         }
128 
129     }
130 
131     private static XMLReader instantiate( String s ){
132         try
133         {
134             Class<?> aClass = Class.forName(  s );
135             return (XMLReader) aClass.newInstance();
136         }
137         catch ( ClassNotFoundException e )
138         {
139             return  null;
140         }
141         catch ( InstantiationException e )
142         {
143             return  null;
144         }
145         catch ( IllegalAccessException e )
146         {
147             return  null;
148         }
149     }
150 
151 
152     private static class DocHandler
153         extends DefaultHandler
154     {
155         private final List<Xpp3Dom> elemStack = new ArrayList<Xpp3Dom>();
156 
157         private final List<StringBuilder> values = new ArrayList<StringBuilder>();
158 
159         // Todo: Use these for something smart !
160         private final List<SAXParseException> warnings = new ArrayList<SAXParseException>();
161 
162         private final List<SAXParseException> errors = new ArrayList<SAXParseException>();
163 
164         private final List<SAXParseException> fatals = new ArrayList<SAXParseException>();
165 
166 
167         Xpp3Dom result = null;
168 
169         private final boolean trim;
170 
171         private boolean spacePreserve = false;
172 
173         DocHandler( boolean trim )
174         {
175             this.trim = trim;
176         }
177 
178         @Override
179         public void startElement( String uri, String localName, String qName, Attributes attributes )
180             throws SAXException
181         {
182             spacePreserve = false;
183             Xpp3Dom child = new Xpp3Dom( localName );
184 
185             attachToParent( child );
186             pushOnStack( child );
187 
188             // Todo: Detecting tags that close immediately seem to be impossible in sax ?
189             // http://stackoverflow.com/questions/12968390/detecting-self-closing-tags-in-sax
190             values.add( new StringBuilder() );
191 
192             int size = attributes.getLength();
193             for ( int i = 0; i < size; i++ )
194             {
195                 String name = attributes.getQName( i );
196                 String value = attributes.getValue( i );
197                 child.setAttribute( name, value );
198                 spacePreserve = spacePreserve || ( "xml:space".equals( name ) && "preserve".equals( value ) );
199             }
200         }
201 
202         private boolean pushOnStack( Xpp3Dom child )
203         {
204             return elemStack.add( child );
205         }
206 
207         private void attachToParent( Xpp3Dom child )
208         {
209             int depth = elemStack.size();
210             if ( depth > 0 )
211             {
212                 elemStack.get( depth - 1 ).addChild( child );
213             }
214         }
215 
216         @Override
217         public void warning( SAXParseException e )
218             throws SAXException
219         {
220             warnings.add( e );
221         }
222 
223         @Override
224         public void error( SAXParseException e )
225             throws SAXException
226         {
227             errors.add( e );
228         }
229 
230         @Override
231         public void fatalError( SAXParseException e )
232             throws SAXException
233         {
234             fatals.add( e );
235         }
236 
237         private Xpp3Dom pop()
238         {
239             int depth = elemStack.size() - 1;
240             return elemStack.remove( depth );
241         }
242 
243         @Override
244         public void endElement( String uri, String localName, String qName )
245             throws SAXException
246         {
247             int depth = elemStack.size() - 1;
248 
249             Xpp3Dom element = pop();
250 
251             /* this Object could be null if it is a singleton tag */
252             Object accumulatedValue = values.remove( depth );
253 
254             if ( element.getChildCount() == 0 )
255             {
256                 if ( accumulatedValue == null )
257                 {
258                     element.setValue( "" ); // null in xpp3dom, but we don't do that around here
259                 }
260                 else
261                 {
262                     element.setValue( accumulatedValue.toString() );
263                 }
264             }
265 
266             if ( depth == 0 )
267             {
268                 result = element;
269             }
270         }
271 
272         @Override
273         public void characters( char[] ch, int start, int length )
274             throws SAXException
275         {
276             String text = new String( ch, start, length );
277             appendToTopValue( ( trim && !spacePreserve ) ? text.trim() : text );
278         }
279 
280         private void appendToTopValue( String toAppend )
281         {
282             // noinspection MismatchedQueryAndUpdateOfStringBuilder
283             StringBuilder stringBuilder = values.get( values.size() - 1 );
284             stringBuilder.append( toAppend );
285         }
286     }
287 
288 }