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              Reader reader = new InputStreamReader(is, encoding);
58              return build( reader, trim);
59          } catch (UnsupportedEncodingException e) {
60              throw new RuntimeException(e);
61          }
62      }
63  
64      public static Xpp3Dom build( @WillClose Reader reader, boolean trim )
65              throws XmlPullParserException
66      {
67          try
68          {
69              DocHandler docHandler = parseSax(new InputSource(reader), trim);
70              return docHandler.result;
71          }
72          finally
73          {
74              IOUtil.close( reader );
75          }
76      }
77  
78      private static DocHandler parseSax(@Nonnull InputSource inputSource, boolean trim) throws XmlPullParserException {
79  
80          try {
81              DocHandler ch = new DocHandler(trim);
82              XMLReader parser = org.xml.sax.helpers.XMLReaderFactory.createXMLReader();
83              parser.setContentHandler( ch);
84              parser.parse(inputSource);
85              return ch;
86          } catch (IOException e){
87              throw new XmlPullParserException(e);
88          } catch (SAXException e) {
89              throw new XmlPullParserException(e);
90          }
91      }
92  
93  
94      private static class DocHandler extends DefaultHandler {
95          private final List<Xpp3Dom> elemStack = new ArrayList<Xpp3Dom>();
96          private final List<StringBuilder> values = new ArrayList<StringBuilder>();
97  
98          // Todo: Use these for something smart !
99          private final List<SAXParseException> warnings = new ArrayList<SAXParseException>();
100         private final List<SAXParseException> errors = new ArrayList<SAXParseException>();
101         private final List<SAXParseException> fatals = new ArrayList<SAXParseException>();
102 
103 
104         Xpp3Dom result = null;
105 
106         private final boolean trim;
107 
108         DocHandler(boolean trim) {
109             this.trim = trim;
110         }
111 
112         @Override
113         public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
114 
115             Xpp3Dom child = new Xpp3Dom( localName );
116 
117             attachToParent(child);
118             pushOnStack(child);
119 
120             // Todo: Detecting tags that close immediately seem to be impossible in sax ?
121             // http://stackoverflow.com/questions/12968390/detecting-self-closing-tags-in-sax
122             values.add( new StringBuilder() );
123 
124             int size = attributes.getLength();
125             for ( int i = 0; i < size; i++ )
126             {
127                 child.setAttribute(attributes.getQName(i), attributes.getValue(i));
128             }
129         }
130 
131         private boolean pushOnStack(Xpp3Dom child) {
132             return elemStack.add(child);
133         }
134 
135         private void attachToParent(Xpp3Dom child) {
136             int depth = elemStack.size();
137             if ( depth > 0 )
138             {
139                 elemStack.get(depth - 1).addChild(child);
140             }
141         }
142 
143         @Override
144         public void warning(SAXParseException e) throws SAXException {
145             warnings.add(e);
146         }
147 
148         @Override
149         public void error(SAXParseException e) throws SAXException {
150             errors.add(e);
151         }
152 
153         @Override
154         public void fatalError(SAXParseException e) throws SAXException {
155             fatals.add(e);
156         }
157 
158 
159         private Xpp3Dom pop()
160         {
161             int depth = elemStack.size() - 1;
162             return elemStack.remove( depth );
163         }
164 
165         @Override
166         public void endElement(String uri, String localName, String qName) throws SAXException {
167             int depth = elemStack.size() - 1;
168 
169             Xpp3Dom finishedConfiguration = pop();
170 
171                 /* this Object could be null if it is a singleton tag */
172             Object accumulatedValue = values.remove( depth );
173 
174             if ( finishedConfiguration.getChildCount() == 0 )
175             {
176                 if ( accumulatedValue == null )
177                 {
178                     finishedConfiguration.setValue( "" ); // null in xpp3dom, but we dont do that around here
179                 }
180                 else
181                 {
182                     finishedConfiguration.setValue( accumulatedValue.toString() );
183                 }
184             }
185 
186             if ( depth == 0 )
187             {
188                 result = finishedConfiguration;
189             }
190         }
191 
192         @Override
193         public void characters(char[] ch, int start, int length) throws SAXException {
194             String text = new String(ch, start, length);
195             appendToTopValue(trim ? text.trim() : text);
196         }
197 
198         private void appendToTopValue(String toAppend) {
199             //noinspection MismatchedQueryAndUpdateOfStringBuilder
200             StringBuilder stringBuilder = values.get(values.size() - 1);
201             stringBuilder.append( toAppend);
202         }
203     }
204 
205 }