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  import java.io.*;
31  import java.util.ArrayList;
32  import java.util.List;
33  
34  /**
35   * @author Kristian Rosenvold
36   */
37  public class Xpp3DomBuilder
38  {
39      private static final boolean DEFAULT_TRIM = true;
40  
41      public static Xpp3Dom build( @WillClose @Nonnull Reader reader )
42          throws XmlPullParserException
43      {
44          return build( reader, DEFAULT_TRIM );
45      }
46  
47      public static Xpp3Dom build( @WillClose InputStream is, @Nonnull String encoding )
48          throws XmlPullParserException
49      {
50          return build( is, encoding, DEFAULT_TRIM );
51      }
52  
53      public static Xpp3Dom build( @WillClose InputStream is, @Nonnull String encoding, boolean trim )
54          throws XmlPullParserException
55      {
56          try
57          {
58              Reader reader = new InputStreamReader( is, encoding );
59              return build( reader, trim );
60          }
61          catch ( UnsupportedEncodingException e )
62          {
63              throw new RuntimeException( e );
64          }
65      }
66  
67      public static Xpp3Dom build( @WillClose Reader reader, boolean trim )
68              throws XmlPullParserException
69      {
70          try
71          {
72              DocHandler docHandler = parseSax( new InputSource( reader ), trim );
73              return docHandler.result;
74          }
75          finally
76          {
77              IOUtil.close( reader );
78          }
79      }
80  
81      private static DocHandler parseSax( @Nonnull InputSource inputSource, boolean trim )
82          throws XmlPullParserException
83      {
84  
85          try
86          {
87              DocHandler ch = new DocHandler( trim );
88              XMLReader parser = org.xml.sax.helpers.XMLReaderFactory.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     private static class DocHandler
104         extends DefaultHandler
105     {
106         private final List<Xpp3Dom> elemStack = new ArrayList<Xpp3Dom>();
107 
108         private final List<StringBuilder> values = new ArrayList<StringBuilder>();
109 
110         // Todo: Use these for something smart !
111         private final List<SAXParseException> warnings = new ArrayList<SAXParseException>();
112         private final List<SAXParseException> errors = new ArrayList<SAXParseException>();
113         private final List<SAXParseException> fatals = new ArrayList<SAXParseException>();
114 
115 
116         Xpp3Dom result = null;
117 
118         private final boolean trim;
119 
120         private boolean spacePreserve = false;
121 
122         DocHandler( boolean trim )
123         {
124             this.trim = trim;
125         }
126 
127         @Override
128         public void startElement( String uri, String localName, String qName, Attributes attributes )
129             throws SAXException
130         {
131             spacePreserve = false;
132             Xpp3Dom child = new Xpp3Dom( localName );
133 
134             attachToParent( child );
135             pushOnStack( child );
136 
137             // Todo: Detecting tags that close immediately seem to be impossible in sax ?
138             // http://stackoverflow.com/questions/12968390/detecting-self-closing-tags-in-sax
139             values.add( new StringBuilder() );
140 
141             int size = attributes.getLength();
142             for ( int i = 0; i < size; i++ )
143             {
144                 String name = attributes.getQName( i );
145                 String value = attributes.getValue( i );
146                 child.setAttribute( name, value );
147                 spacePreserve = spacePreserve || ( "xml:space".equals( name ) && "preserve".equals( value ) );
148             }
149         }
150 
151         private boolean pushOnStack( Xpp3Dom child )
152         {
153             return elemStack.add( child );
154         }
155 
156         private void attachToParent( Xpp3Dom child )
157         {
158             int depth = elemStack.size();
159             if ( depth > 0 )
160             {
161                 elemStack.get( depth - 1 ).addChild( child );
162             }
163         }
164 
165         @Override
166         public void warning( SAXParseException e )
167             throws SAXException
168         {
169             warnings.add( e );
170         }
171 
172         @Override
173         public void error( SAXParseException e )
174             throws SAXException
175         {
176             errors.add( e );
177         }
178 
179         @Override
180         public void fatalError( SAXParseException e )
181             throws SAXException
182         {
183             fatals.add( e );
184         }
185 
186         private Xpp3Dom pop()
187         {
188             int depth = elemStack.size() - 1;
189             return elemStack.remove( depth );
190         }
191 
192         @Override
193         public void endElement( String uri, String localName, String qName )
194             throws SAXException
195         {
196             int depth = elemStack.size() - 1;
197 
198             Xpp3Dom element = pop();
199 
200             /* this Object could be null if it is a singleton tag */
201             Object accumulatedValue = values.remove( depth );
202 
203             if ( element.getChildCount() == 0 )
204             {
205                 if ( accumulatedValue == null )
206                 {
207                     element.setValue( "" ); // null in xpp3dom, but we don't do that around here
208                 }
209                 else
210                 {
211                     element.setValue( accumulatedValue.toString() );
212                 }
213             }
214 
215             if ( depth == 0 )
216             {
217                 result = element;
218             }
219         }
220 
221         @Override
222         public void characters( char[] ch, int start, int length )
223             throws SAXException
224         {
225             String text = new String( ch, start, length );
226             appendToTopValue( ( trim && !spacePreserve ) ? text.trim() : text );
227         }
228 
229         private void appendToTopValue( String toAppend )
230         {
231             // noinspection MismatchedQueryAndUpdateOfStringBuilder
232             StringBuilder stringBuilder = values.get( values.size() - 1 );
233             stringBuilder.append( toAppend );
234         }
235     }
236 
237 }