View Javadoc
1   package org.exolab.castor.xml;
2   
3   import java.util.List;
4   import java.util.Stack;
5   
6   import javax.xml.namespace.QName;
7   import javax.xml.stream.Location;
8   import javax.xml.stream.XMLEventReader;
9   import javax.xml.stream.XMLStreamConstants;
10  import javax.xml.stream.XMLStreamReader;
11  
12  import org.apache.commons.logging.Log;
13  import org.apache.commons.logging.LogFactory;
14  import org.xml.sax.Attributes;
15  import org.xml.sax.ContentHandler;
16  import org.xml.sax.ErrorHandler;
17  import org.xml.sax.Locator;
18  import org.xml.sax.SAXException;
19  
20  /**
21   * This provides shared code for {@link Sax2EventFromStaxEventProducer} and
22   * {@link Sax2EventFromStaxStreamProducer}. It consumes StAX events and produces SAX2 events.
23   * 
24   * @author <a href="mailto:philipp DOT erlacher AT gmail DOT com">Philipp Erlacher</a>
25   * 
26   */
27  public abstract class BaseSax2EventFromStaxProducer implements SAX2EventAndErrorProducer {
28  
29    /**
30     * Logger from commons-logging.
31     */
32    private static final Log LOG = LogFactory.getLog(BaseSax2EventFromStaxProducer.class);
33  
34    /**
35     * A stack to keep track when it's time to invoke endPrefixMapping
36     */
37    private Stack<List<String>> prefixes = new Stack<List<String>>();
38  
39  
40    /**
41     * On this interface the SAX methods get invoked
42     */
43    private ContentHandler contentHandler;
44  
45    /**
46     * Callback Interface to handle errors
47     */
48    private ErrorHandler errorHandler;
49  
50    public static SAX2EventAndErrorProducer createSax2EventFromStax(XMLStreamReader streamReader) {
51      return new Sax2EventFromStaxStreamProducer(streamReader);
52    }
53  
54    public static SAX2EventAndErrorProducer createSax2EventFromStax(XMLEventReader eventReader) {
55      return new Sax2EventFromStaxEventProducer(eventReader);
56    }
57  
58    public void setContentHandler(ContentHandler contentHandler) {
59      this.contentHandler = contentHandler;
60    }
61  
62    public void setErrorHandler(ErrorHandler errorHandler) {
63      this.errorHandler = errorHandler;
64    }
65  
66    public Stack<List<String>> getPrefixes() {
67      return prefixes;
68    }
69  
70    public ContentHandler getContentHandler() {
71      return contentHandler;
72    }
73  
74    public ErrorHandler getErrorHandler() {
75      return errorHandler;
76    }
77  
78    /**
79     * This method takes an eventType and invokes a method to handle that event.
80     * 
81     * <p>
82     * It also takes information about the depth of the read element. Maybe depth changes due to
83     * handling that event.
84     * </p>
85     * 
86     * @param eventType The event type
87     * @param depth The current depth of the element
88     * @return depth The updated depth
89     * @throws SAXException
90     */
91    int handleEventType(int eventType, int depth) throws SAXException {
92      switch (eventType) {
93        case XMLStreamConstants.START_ELEMENT:
94          handleStartElement();
95          return ++depth;
96        case XMLStreamConstants.END_ELEMENT:
97          handleEndElement();
98          return --depth;
99        case XMLStreamConstants.START_DOCUMENT:
100         handleStartDocument();
101         return ++depth;
102       case XMLStreamConstants.END_DOCUMENT:
103         handleEndDocument();
104         return --depth;
105       case XMLStreamConstants.CHARACTERS:
106         handleCharacters();
107         return depth;
108       case XMLStreamConstants.SPACE:
109         handleSpace();
110         return depth;
111       default:
112         return depth;
113     }
114   }
115 
116   /**
117    * Invoke {@link #handleDocumentLocator()} and {@link getContentHandler().startDocument()};
118    * 
119    * @throws SAXException
120    */
121   void handleStartDocument() throws SAXException {
122     LOG.info("< handleStartDocument >");
123 
124     handleDocumentLocator();
125 
126     contentHandler.startDocument();
127   }
128 
129   /**
130    * Handles a end document event.
131    * <p>
132    * Invoke {@link getContentHandler().endDocument()};
133    * </p>
134    * 
135    * @throws SAXException
136    */
137   void handleEndDocument() throws SAXException {
138     LOG.info("< handleEndDocument >");
139     contentHandler.endDocument();
140 
141   }
142 
143   /**
144    * Handles a start element event.
145    * <p>
146    * Invoke {@link #doStartPrefixMapping()} and {@link getContentHandler().startElement()};
147    * </p>
148    * 
149    * @throws SAXException
150    */
151   void handleStartElement() throws SAXException {
152     LOG.info("< handleStartElement >");
153 
154     QName qName = getQName();
155     String localName = qName.getLocalPart();
156     String uri = qName.getNamespaceURI();
157     String prefix = qName.getPrefix();
158     String qNameString = getQName(prefix, localName);
159 
160     Attributes atts = getAttributes();
161 
162     doStartPrefixMapping();
163 
164     contentHandler.startElement(uri, localName, qNameString, atts);
165 
166   }
167 
168   /**
169    * Handles an end element event.
170    * <p>
171    * Invoke {@link getContentHandler().endElement()} and {@link #doEndPrefixMapping()};
172    * </p>
173    * 
174    * @throws SAXException
175    */
176   void handleEndElement() throws SAXException {
177     LOG.info("< handleEndElement >");
178 
179     QName qName = getQName();
180     String localName = qName.getLocalPart();
181     String uri = qName.getNamespaceURI();
182     String prefix = qName.getPrefix();
183     String qNameString = getQName(prefix, localName);
184 
185     contentHandler.endElement(uri, localName, qNameString);
186 
187     doEndPrefixMapping();
188   }
189 
190   /**
191    * Handles a space event.
192    * 
193    * @throws SAXException
194    */
195   void handleSpace() throws SAXException {
196 
197   }
198 
199   /**
200    * Handles a character event.
201    * <p>
202    * If chars is ignorable whitespace {@link getContentHandler().ignorableWhitespace will be called.
203    * Otherwise {@link getContentHandler().characters()} will be called with characters(char[], 0,
204    * length)
205    * 
206    * </p>
207    * 
208    * @throws SAXException
209    */
210   void handleCharacters() throws SAXException {
211     LOG.info("< handleCharacters >");
212     char[] chars;
213     chars = getCharacters();
214 
215     if (isIgnorableWhitespace(chars, 0, chars.length))
216       contentHandler.ignorableWhitespace(chars, 0, chars.length);
217     else
218       contentHandler.characters(chars, 0, chars.length);
219 
220   }
221 
222   /**
223    * @param prefix
224    * @param localPart
225    * @return qName. If prefix length >=1 then it's like prefix:localPart, otherwise it's just the
226    *         localPart
227    */
228   String getQName(String prefix, String localPart) {
229     return getNonEmpty(prefix).length() >= 1 ? prefix + ":" + localPart : localPart;
230   }
231 
232   /**
233    * If a chars without leading and trailing whitespaces would be empty, this method returns true,
234    * otherwise false,
235    * 
236    * @param chars
237    * @param start the offset
238    * @param length
239    * @return
240    */
241   boolean isIgnorableWhitespace(char[] chars, int start, int length) {
242     String string = new String(chars, start, length);
243     return string.trim().length() == 0;
244   }
245 
246   /**
247    * If string equals null this returns an empty string, otherwise it returns the string
248    * 
249    * @param string the string to check
250    * @return a string. If string equals null this returns an empty string, otherwise it returns the
251    *         string
252    */
253   String getNonEmpty(String string) {
254     return string == null ? "" : string;
255   }
256 
257   /**
258    * @return a Location
259    */
260   abstract Location getLocation();
261 
262   /**
263    * @return characters of the current event.
264    */
265   abstract char[] getCharacters();
266 
267   /**
268    * For every declared namespace in the current event
269    * {@link getContentHandler().startPrefixMapping()} gets invoked.
270    * 
271    * @throws SAXException
272    */
273   abstract void doStartPrefixMapping() throws SAXException;
274 
275   /**
276    * 
277    * @throws SAXException
278    */
279   abstract void doEndPrefixMapping() throws SAXException;
280 
281   /**
282    * @return attributes of the current event
283    */
284   abstract Attributes getAttributes();
285 
286   /**
287    * @return QName of the current event
288    */
289   abstract QName getQName();
290 
291   /**
292    * If {@link #getLocation()} gets a location {@link getContentHandler().setDocumentLocator()} will
293    * be called, otherwise not.
294    */
295   private void handleDocumentLocator() {
296     Locator locator = getSAXLocator(getLocation());
297     contentHandler.setDocumentLocator(locator);
298   }
299 
300   /**
301    * Gets a {@link org.xml.sax.Locator} to a given {@link Location}.
302    * 
303    * @param location A {@link Location}
304    * @return A {@link Locator}
305    */
306   protected Locator getSAXLocator(Location location) {
307     return new Locator() {
308       public String getSystemId() {
309         return getLocation().getSystemId();
310       }
311 
312       public String getPublicId() {
313         return getLocation().getPublicId();
314       }
315 
316       public int getLineNumber() {
317         return getLocation().getLineNumber();
318       }
319 
320       public int getColumnNumber() {
321         return getLocation().getColumnNumber();
322       }
323     };
324   }
325 }