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.xml.internal.org.jvnet.staxex.util; 27 28 import java.io.IOException; 29 30 import javax.xml.bind.attachment.AttachmentMarshaller; 31 import javax.xml.stream.XMLStreamConstants; 32 import javax.xml.stream.XMLStreamException; 33 import javax.xml.stream.XMLStreamReader; 34 import javax.xml.stream.XMLStreamWriter; 35 import javax.xml.XMLConstants; 36 37 import com.sun.xml.internal.org.jvnet.staxex.Base64Data; 38 import com.sun.xml.internal.org.jvnet.staxex.XMLStreamReaderEx; 39 import com.sun.xml.internal.org.jvnet.staxex.XMLStreamWriterEx; 40 41 /** 42 * Reads a sub-tree from {@link XMLStreamReader} and writes to {@link XMLStreamWriter} 43 * as-is. 44 * 45 * <p> 46 * This class can be sub-classed to implement a simple transformation logic. 47 * 48 * @author Kohsuke Kawaguchi 49 * @author Ryan Shoemaker 50 */ 51 public class XMLStreamReaderToXMLStreamWriter { 52 53 static public class Breakpoint { 54 protected XMLStreamReader reader; 55 protected XMLStreamWriter writer; 56 57 public Breakpoint(XMLStreamReader r, XMLStreamWriter w) { reader = r; writer = w; } 58 59 public XMLStreamReader reader() { return reader; } 60 public XMLStreamWriter writer() { return writer; } 61 public boolean proceedBeforeStartElement() { return true; } 62 public boolean proceedAfterStartElement() { return true; } 63 } 64 65 private static final int BUF_SIZE = 4096; 66 67 protected XMLStreamReader in; 68 protected XMLStreamWriter out; 69 70 private char[] buf; 71 72 boolean optimizeBase64Data = false; 73 74 AttachmentMarshaller mtomAttachmentMarshaller; 75 76 /** 77 * Reads one subtree and writes it out. 78 * 79 * <p> 80 * The {@link XMLStreamWriter} never receives a start/end document event. 81 * Those need to be written separately by the caller. 82 */ 83 public void bridge(XMLStreamReader in, XMLStreamWriter out) throws XMLStreamException { 84 bridge(in, out, null); 85 } 86 87 public void bridge(Breakpoint breakPoint) throws XMLStreamException { 88 bridge(breakPoint.reader(), breakPoint.writer(), breakPoint); 89 } 90 91 private void bridge(XMLStreamReader in, XMLStreamWriter out, Breakpoint breakPoint) throws XMLStreamException { 92 assert in!=null && out!=null; 93 this.in = in; 94 this.out = out; 95 96 optimizeBase64Data = (in instanceof XMLStreamReaderEx); 97 98 if (out instanceof XMLStreamWriterEx && out instanceof MtomStreamWriter) { 99 mtomAttachmentMarshaller = ((MtomStreamWriter) out).getAttachmentMarshaller(); 100 } 101 // remembers the nest level of elements to know when we are done. 102 int depth=0; 103 104 buf = new char[BUF_SIZE]; 105 106 // if the parser is at the start tag, proceed to the first element 107 int event = getEventType(); 108 109 if( event!=XMLStreamConstants.START_ELEMENT) 110 throw new IllegalStateException("The current event is not START_ELEMENT\n but " + event); 111 112 do { 113 // These are all of the events listed in the javadoc for 114 // XMLEvent. 115 // The spec only really describes 11 of them. 116 switch (event) { 117 case XMLStreamConstants.START_ELEMENT : 118 if (breakPoint != null && !breakPoint.proceedBeforeStartElement()) return; 119 depth++; 120 handleStartElement(); 121 if (breakPoint != null && !breakPoint.proceedAfterStartElement()) return; 122 break; 123 case XMLStreamConstants.END_ELEMENT : 124 handleEndElement(); 125 depth--; 126 if(depth==0) 127 return; 128 break; 129 case XMLStreamConstants.CHARACTERS : 130 handleCharacters(); 131 break; 132 case XMLStreamConstants.ENTITY_REFERENCE : 133 handleEntityReference(); 134 break; 135 case XMLStreamConstants.PROCESSING_INSTRUCTION : 136 handlePI(); 137 break; 138 case XMLStreamConstants.COMMENT : 139 handleComment(); 140 break; 141 case XMLStreamConstants.DTD : 142 handleDTD(); 143 break; 144 case XMLStreamConstants.CDATA : 145 handleCDATA(); 146 break; 147 case XMLStreamConstants.SPACE : 148 handleSpace(); 149 break; 150 case XMLStreamConstants.END_DOCUMENT: 151 throw new XMLStreamException("Malformed XML at depth="+depth+", Reached EOF. Event="+event); 152 default : 153 throw new XMLStreamException("Cannot process event: " + event); 154 } 155 156 event=getNextEvent(); 157 } while (depth!=0); 158 } 159 160 protected void handlePI() throws XMLStreamException { 161 out.writeProcessingInstruction( 162 in.getPITarget(), 163 in.getPIData()); 164 } 165 166 167 protected void handleCharacters() throws XMLStreamException { 168 169 CharSequence c = null; 170 171 if (optimizeBase64Data) { 172 c = ((XMLStreamReaderEx)in).getPCDATA(); 173 } 174 175 if ((c != null) && (c instanceof Base64Data)) { 176 if (mtomAttachmentMarshaller != null) { 177 Base64Data b64d = (Base64Data) c; 178 ((XMLStreamWriterEx)out).writeBinary(b64d.getDataHandler()); 179 } else { 180 try { 181 ((Base64Data)c).writeTo(out); 182 } catch (IOException e) { 183 throw new XMLStreamException(e); 184 } 185 } 186 } else { 187 for (int start=0,read=buf.length; read == buf.length; start+=buf.length) { 188 read = in.getTextCharacters(start, buf, 0, buf.length); 189 out.writeCharacters(buf, 0, read); 190 } 191 } 192 } 193 194 protected void handleEndElement() throws XMLStreamException { 195 out.writeEndElement(); 196 } 197 198 protected void handleStartElement() throws XMLStreamException { 199 String nsUri = in.getNamespaceURI(); 200 if(nsUri==null) 201 out.writeStartElement(in.getLocalName()); 202 else 203 out.writeStartElement( 204 fixNull(in.getPrefix()), 205 in.getLocalName(), 206 nsUri 207 ); 208 209 // start namespace bindings 210 int nsCount = in.getNamespaceCount(); 211 for (int i = 0; i < nsCount; i++) { 212 out.writeNamespace( 213 fixNull(in.getNamespacePrefix(i)), //StAX reader will return null for default NS 214 fixNull(in.getNamespaceURI(i))); // zephyr doesn't like null, I don't know what is correct, so just fix null to "" for now 215 } 216 217 // write attributes 218 int attCount = in.getAttributeCount(); 219 for (int i = 0; i < attCount; i++) { 220 handleAttribute(i); 221 } 222 } 223 224 /** 225 * Writes out the {@code i}-th attribute of the current element. 226 * 227 * <p> 228 * Used from {@link #handleStartElement()}. 229 */ 230 protected void handleAttribute(int i) throws XMLStreamException { 231 String nsUri = in.getAttributeNamespace(i); 232 String prefix = in.getAttributePrefix(i); 233 if (fixNull(nsUri).equals(XMLConstants.XMLNS_ATTRIBUTE_NS_URI)) { 234 //Its a namespace decl, ignore as it is already written. 235 return; 236 } 237 238 if(nsUri==null || prefix == null || prefix.equals("")) { 239 out.writeAttribute( 240 in.getAttributeLocalName(i), 241 in.getAttributeValue(i) 242 ); 243 } else { 244 out.writeAttribute( 245 prefix, 246 nsUri, 247 in.getAttributeLocalName(i), 248 in.getAttributeValue(i) 249 ); 250 } 251 } 252 253 protected void handleDTD() throws XMLStreamException { 254 out.writeDTD(in.getText()); 255 } 256 257 protected void handleComment() throws XMLStreamException { 258 out.writeComment(in.getText()); 259 } 260 261 protected void handleEntityReference() throws XMLStreamException { 262 out.writeEntityRef(in.getText()); 263 } 264 265 protected void handleSpace() throws XMLStreamException { 266 handleCharacters(); 267 } 268 269 protected void handleCDATA() throws XMLStreamException { 270 out.writeCData(in.getText()); 271 } 272 273 private static String fixNull(String s) { 274 if(s==null) return ""; 275 else return s; 276 } 277 278 private int getEventType() throws XMLStreamException { 279 int event = in.getEventType(); 280 // if the parser is at the start tag, proceed to the first element 281 //Note - need to do this every time because we could be using a composite reader 282 if(event == XMLStreamConstants.START_DOCUMENT) { 283 // nextTag doesn't correctly handle DTDs 284 while( !in.isStartElement() ) { 285 event = in.next(); 286 if (event == XMLStreamConstants.COMMENT) 287 handleComment(); 288 } 289 } 290 return event; 291 } 292 293 private int getNextEvent() throws XMLStreamException { 294 in.next(); 295 return getEventType(); 296 } 297 }