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 
  26 package com.sun.istack.internal;
  27 
  28 import org.xml.sax.ContentHandler;
  29 import org.xml.sax.SAXException;
  30 import org.xml.sax.Locator;
  31 import org.xml.sax.Attributes;
  32 import org.xml.sax.helpers.AttributesImpl;
  33 
  34 import javax.xml.stream.XMLStreamReader;
  35 import javax.xml.stream.XMLStreamException;
  36 import javax.xml.stream.XMLStreamConstants;
  37 import 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  */
  48 public 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 }