1/*
2 * Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package com.sun.istack.internal;
27
28import org.xml.sax.ContentHandler;
29import org.xml.sax.SAXException;
30import org.xml.sax.Locator;
31import org.xml.sax.Attributes;
32import org.xml.sax.helpers.AttributesImpl;
33
34import javax.xml.stream.XMLStreamReader;
35import javax.xml.stream.XMLStreamException;
36import javax.xml.stream.XMLStreamConstants;
37import javax.xml.namespace.QName;
38
39/**
40 * This is a simple utility class that adapts StAX events from an
41 * {@link XMLStreamReader} to SAX events on a
42 * {@link ContentHandler}, bridging between the two
43 * parser technologies.
44 *
45 * @author Ryan.Shoemaker@Sun.COM
46 * @version 1.0
47 */
48public class XMLStreamReaderToContentHandler {
49
50    // StAX event source
51    private final XMLStreamReader staxStreamReader;
52
53    // SAX event sink
54    private final ContentHandler saxHandler;
55
56    // if true, when the conversion is completed, leave the cursor to the last
57    // event that was fired (such as end element)
58    private final boolean eagerQuit;
59
60    /**
61     * If true, not start/endDocument event.
62     */
63    private final boolean fragment;
64
65    // array of the even length of the form { prefix0, uri0, prefix1, uri1, ... }
66    private final String[] inscopeNamespaces;
67
68    /**
69     * @see #XMLStreamReaderToContentHandler(XMLStreamReader, ContentHandler, boolean, boolean, String[])
70     */
71    public XMLStreamReaderToContentHandler(XMLStreamReader staxCore, ContentHandler saxCore, boolean eagerQuit, boolean fragment) {
72        this(staxCore, saxCore, eagerQuit, fragment, new String[0]);
73    }
74
75    /**
76     * Construct a new StAX to SAX adapter that will convert a StAX event
77     * stream into a SAX event stream.
78     *
79     * @param staxCore
80     *                StAX event source
81     * @param saxCore
82     *                SAXevent sink
83     * @param eagerQuit
84     * @param fragment
85     * @param inscopeNamespaces
86     *                array of the even length of the form { prefix0, uri0, prefix1, uri1, ... }
87     */
88    public XMLStreamReaderToContentHandler(XMLStreamReader staxCore, ContentHandler saxCore,
89            boolean eagerQuit, boolean fragment, String[] inscopeNamespaces) {
90        this.staxStreamReader = staxCore;
91        this.saxHandler = saxCore;
92        this.eagerQuit = eagerQuit;
93        this.fragment = fragment;
94        this.inscopeNamespaces = inscopeNamespaces.clone();
95        assert inscopeNamespaces.length%2 == 0;
96    }
97
98
99    /*
100     * @see StAXReaderToContentHandler#bridge()
101     */
102    public void bridge() throws XMLStreamException {
103
104        try {
105            // remembers the nest level of elements to know when we are done.
106            int depth=0;
107
108            // if the parser is at the start tag, proceed to the first element
109            int event = staxStreamReader.getEventType();
110            if(event == XMLStreamConstants.START_DOCUMENT) {
111                // nextTag doesn't correctly handle DTDs
112                while( !staxStreamReader.isStartElement() )
113                    event = staxStreamReader.next();
114            }
115
116
117            if( event!=XMLStreamConstants.START_ELEMENT)
118                throw new IllegalStateException("The current event is not START_ELEMENT\n but " + event);
119
120            handleStartDocument();
121
122            for(int i=0; i < inscopeNamespaces.length; i+=2) {
123                saxHandler.startPrefixMapping(inscopeNamespaces[i], inscopeNamespaces[i+1]);
124            }
125
126            OUTER:
127            do {
128                // These are all of the events listed in the javadoc for
129                // XMLEvent.
130                // The spec only really describes 11 of them.
131                switch (event) {
132                    case XMLStreamConstants.START_ELEMENT :
133                        depth++;
134                        handleStartElement();
135                        break;
136                    case XMLStreamConstants.END_ELEMENT :
137                        handleEndElement();
138                        depth--;
139                        if(depth==0 && eagerQuit)
140                            break OUTER;
141                        break;
142                    case XMLStreamConstants.CHARACTERS :
143                        handleCharacters();
144                        break;
145                    case XMLStreamConstants.ENTITY_REFERENCE :
146                        handleEntityReference();
147                        break;
148                    case XMLStreamConstants.PROCESSING_INSTRUCTION :
149                        handlePI();
150                        break;
151                    case XMLStreamConstants.COMMENT :
152                        handleComment();
153                        break;
154                    case XMLStreamConstants.DTD :
155                        handleDTD();
156                        break;
157                    case XMLStreamConstants.ATTRIBUTE :
158                        handleAttribute();
159                        break;
160                    case XMLStreamConstants.NAMESPACE :
161                        handleNamespace();
162                        break;
163                    case XMLStreamConstants.CDATA :
164                        handleCDATA();
165                        break;
166                    case XMLStreamConstants.ENTITY_DECLARATION :
167                        handleEntityDecl();
168                        break;
169                    case XMLStreamConstants.NOTATION_DECLARATION :
170                        handleNotationDecl();
171                        break;
172                    case XMLStreamConstants.SPACE :
173                        handleSpace();
174                        break;
175                    default :
176                        throw new InternalError("processing event: " + event);
177                }
178
179                event=staxStreamReader.next();
180            } while (depth!=0);
181
182            for(int i=0; i < inscopeNamespaces.length; i+=2) {
183                saxHandler.endPrefixMapping(inscopeNamespaces[i]);
184            }
185
186            handleEndDocument();
187        } catch (SAXException e) {
188            throw new XMLStreamException2(e);
189        }
190    }
191
192    private void handleEndDocument() throws SAXException {
193        if(fragment)
194            return;
195
196        saxHandler.endDocument();
197    }
198
199    private void handleStartDocument() throws SAXException {
200        if(fragment)
201            return;
202
203        saxHandler.setDocumentLocator(new Locator() {
204            public int getColumnNumber() {
205                return staxStreamReader.getLocation().getColumnNumber();
206            }
207            public int getLineNumber() {
208                return staxStreamReader.getLocation().getLineNumber();
209            }
210            public String getPublicId() {
211                return staxStreamReader.getLocation().getPublicId();
212            }
213            public String getSystemId() {
214                return staxStreamReader.getLocation().getSystemId();
215            }
216        });
217        saxHandler.startDocument();
218    }
219
220    private void handlePI() throws XMLStreamException {
221        try {
222            saxHandler.processingInstruction(
223                staxStreamReader.getPITarget(),
224                staxStreamReader.getPIData());
225        } catch (SAXException e) {
226            throw new XMLStreamException2(e);
227        }
228    }
229
230    private void handleCharacters() throws XMLStreamException {
231        try {
232            saxHandler.characters(
233                staxStreamReader.getTextCharacters(),
234                staxStreamReader.getTextStart(),
235                staxStreamReader.getTextLength() );
236        } catch (SAXException e) {
237            throw new XMLStreamException2(e);
238        }
239    }
240
241    private void handleEndElement() throws XMLStreamException {
242        QName qName = staxStreamReader.getName();
243
244        try {
245            String pfix = qName.getPrefix();
246            String rawname = (pfix == null || pfix.length() == 0)
247                    ? qName.getLocalPart()
248                    : pfix + ':' + qName.getLocalPart();
249            // fire endElement
250            saxHandler.endElement(
251                qName.getNamespaceURI(),
252                qName.getLocalPart(),
253                rawname);
254
255            // end namespace bindings
256            int nsCount = staxStreamReader.getNamespaceCount();
257            for (int i = nsCount - 1; i >= 0; i--) {
258                String prefix = staxStreamReader.getNamespacePrefix(i);
259                if (prefix == null) { // true for default namespace
260                    prefix = "";
261                }
262                saxHandler.endPrefixMapping(prefix);
263            }
264        } catch (SAXException e) {
265            throw new XMLStreamException2(e);
266        }
267    }
268
269    private void handleStartElement() throws XMLStreamException {
270
271        try {
272            // start namespace bindings
273            int nsCount = staxStreamReader.getNamespaceCount();
274            for (int i = 0; i < nsCount; i++) {
275                saxHandler.startPrefixMapping(
276                    fixNull(staxStreamReader.getNamespacePrefix(i)),
277                    fixNull(staxStreamReader.getNamespaceURI(i)));
278            }
279
280            // fire startElement
281            QName qName = staxStreamReader.getName();
282            String prefix = qName.getPrefix();
283            String rawname;
284            if(prefix==null || prefix.length()==0)
285                rawname = qName.getLocalPart();
286            else
287                rawname = prefix + ':' + qName.getLocalPart();
288            Attributes attrs = getAttributes();
289            saxHandler.startElement(
290                qName.getNamespaceURI(),
291                qName.getLocalPart(),
292                rawname,
293                attrs);
294        } catch (SAXException e) {
295            throw new XMLStreamException2(e);
296        }
297    }
298
299    private static String fixNull(String s) {
300        if(s==null)     return "";
301        else            return s;
302    }
303
304    /**
305     * Get the attributes associated with the given START_ELEMENT or ATTRIBUTE
306     * StAXevent.
307     *
308     * @return the StAX attributes converted to an org.xml.sax.Attributes
309     */
310    private Attributes getAttributes() {
311        AttributesImpl attrs = new AttributesImpl();
312
313        int eventType = staxStreamReader.getEventType();
314        if (eventType != XMLStreamConstants.ATTRIBUTE
315            && eventType != XMLStreamConstants.START_ELEMENT) {
316            throw new InternalError(
317                "getAttributes() attempting to process: " + eventType);
318        }
319
320        // in SAX, namespace declarations are not part of attributes by default.
321        // (there's a property to control that, but as far as we are concerned
322        // we don't use it.) So don't add xmlns:* to attributes.
323
324        // gather non-namespace attrs
325        for (int i = 0; i < staxStreamReader.getAttributeCount(); i++) {
326            String uri = staxStreamReader.getAttributeNamespace(i);
327            if(uri==null)   uri="";
328            String localName = staxStreamReader.getAttributeLocalName(i);
329            String prefix = staxStreamReader.getAttributePrefix(i);
330            String qName;
331            if(prefix==null || prefix.length()==0)
332                qName = localName;
333            else
334                qName = prefix + ':' + localName;
335            String type = staxStreamReader.getAttributeType(i);
336            String value = staxStreamReader.getAttributeValue(i);
337
338            attrs.addAttribute(uri, localName, qName, type, value);
339        }
340
341        return attrs;
342    }
343
344    private void handleNamespace() {
345        // no-op ???
346        // namespace events don't normally occur outside of a startElement
347        // or endElement
348    }
349
350    private void handleAttribute() {
351        // no-op ???
352        // attribute events don't normally occur outside of a startElement
353        // or endElement
354    }
355
356    private void handleDTD() {
357        // no-op ???
358        // it seems like we need to pass this info along, but how?
359    }
360
361    private void handleComment() {
362        // no-op ???
363    }
364
365    private void handleEntityReference() {
366        // no-op ???
367    }
368
369    private void handleSpace() {
370        // no-op ???
371        // this event is listed in the javadoc, but not in the spec.
372    }
373
374    private void handleNotationDecl() {
375        // no-op ???
376        // this event is listed in the javadoc, but not in the spec.
377    }
378
379    private void handleEntityDecl() {
380        // no-op ???
381        // this event is listed in the javadoc, but not in the spec.
382    }
383
384    private void handleCDATA() {
385        // no-op ???
386        // this event is listed in the javadoc, but not in the spec.
387    }
388}
389