1 /*
   2  * Copyright (c) 2005, 2020, 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.org.apache.xalan.internal.xsltc.trax;
  27 
  28 import java.util.ArrayList;
  29 import java.util.Collection;
  30 import java.util.Collections;
  31 import java.util.HashMap;
  32 import java.util.Iterator;
  33 import java.util.List;
  34 import java.util.Map;
  35 import javax.xml.stream.XMLEventFactory;
  36 import javax.xml.stream.XMLEventWriter;
  37 import javax.xml.stream.XMLStreamException;
  38 import javax.xml.stream.events.*;
  39 import org.xml.sax.Attributes;
  40 import org.xml.sax.SAXException;
  41 
  42 /**
  43  * @author Sunitha Reddy
  44  */
  45 public class SAX2StAXEventWriter extends SAX2StAXBaseWriter {
  46 
  47     private XMLEventWriter writer;
  48 
  49     private XMLEventFactory eventFactory;
  50 
  51     private List<Collection<Namespace>> namespaceStack = new ArrayList<>();
  52 
  53     private boolean needToCallStartDocument = false;
  54 
  55     public SAX2StAXEventWriter() {
  56         eventFactory = XMLEventFactory.newInstance();
  57     }
  58 
  59     public SAX2StAXEventWriter(XMLEventWriter writer) {
  60         this.writer = writer;
  61         eventFactory = XMLEventFactory.newInstance();
  62     }
  63 
  64     public SAX2StAXEventWriter(XMLEventWriter writer,
  65             XMLEventFactory factory) {
  66 
  67         this.writer = writer;
  68         if (factory != null) {
  69             this.eventFactory = factory;
  70         } else {
  71             eventFactory = XMLEventFactory.newInstance();
  72         }
  73     }
  74 
  75     public XMLEventWriter getEventWriter() {
  76         return writer;
  77     }
  78 
  79     public void setEventWriter(XMLEventWriter writer) {
  80         this.writer = writer;
  81     }
  82 
  83     public XMLEventFactory getEventFactory() {
  84         return eventFactory;
  85     }
  86 
  87     public void setEventFactory(XMLEventFactory factory) {
  88         this.eventFactory = factory;
  89     }
  90 
  91     public void startDocument() throws SAXException {
  92         super.startDocument();
  93         namespaceStack.clear();
  94         eventFactory.setLocation(getCurrentLocation());
  95 
  96         // Encoding and version info will be available only after startElement
  97         // is called for first time. So, defer START_DOCUMENT event of StAX till
  98         // that point of time.
  99         needToCallStartDocument = true;
 100     }
 101 
 102     void writeStartDocument() throws SAXException {
 103         super.writeStartDocument();
 104         try {
 105             writer.add(eventFactory.createStartDocument(encoding, xmlVersion));
 106         } catch (XMLStreamException e) {
 107             throw new SAXException(e);
 108         }
 109         needToCallStartDocument = false;
 110     }
 111 
 112     public void endDocument() throws SAXException {
 113         eventFactory.setLocation(getCurrentLocation());
 114 
 115         try {
 116             writer.add(eventFactory.createEndDocument());
 117         } catch (XMLStreamException e) {
 118             throw new SAXException(e);
 119         }
 120 
 121         super.endDocument();
 122 
 123         // clear the namespaces
 124         namespaceStack.clear();
 125 
 126     }
 127 
 128     @SuppressWarnings({"rawtypes", "unchecked"})
 129     public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
 130         if (needToCallStartDocument) {
 131             writeStartDocument();
 132         }
 133 
 134         // set document location
 135         eventFactory.setLocation(getCurrentLocation());
 136 
 137         // create attribute and namespace events
 138         Collection[] events = {null, null};
 139         createStartEvents(attributes, events);
 140 
 141         namespaceStack.add(events[0]);
 142 
 143         try {
 144             String[] qname = {null, null};
 145             parseQName(qName, qname);
 146 
 147             writer.add(eventFactory.createStartElement(qname[0], uri,
 148                     qname[1], events[1].iterator(), events[0].iterator()));
 149         } catch (XMLStreamException e) {
 150             throw new SAXException(e);
 151         } finally {
 152             super.startElement(uri, localName, qName, attributes);
 153         }
 154 
 155     }
 156 
 157     public void endElement(String uri, String localName, String qName)
 158             throws SAXException {
 159 
 160         super.endElement(uri, localName, qName);
 161 
 162         eventFactory.setLocation(getCurrentLocation());
 163 
 164         // parse name
 165         String[] qname = {null, null};
 166         parseQName(qName, qname);
 167 
 168         // get namespaces
 169         Collection<Namespace> nsList = namespaceStack.remove(namespaceStack.size() - 1);
 170         Iterator<Namespace> nsIter = nsList.iterator();
 171 
 172         try {
 173             writer.add(eventFactory.createEndElement(qname[0], uri, qname[1],
 174                     nsIter));
 175         } catch (XMLStreamException e) {
 176             throw new SAXException(e);
 177         }
 178     }
 179 
 180     public void comment(char[] ch, int start, int length) throws SAXException {
 181         if (needToCallStartDocument) {
 182             // Drat. We were trying to postpone this until the first element so that we could get
 183             // the locator, but we can't output a comment before the start document, so we're just
 184             // going to have to do without the locator if it hasn't been set yet.
 185             writeStartDocument();
 186         }
 187 
 188         super.comment(ch, start, length);
 189 
 190         eventFactory.setLocation(getCurrentLocation());
 191         try {
 192             writer.add(eventFactory.createComment(new String(ch, start,
 193                     length)));
 194         } catch (XMLStreamException e) {
 195             throw new SAXException(e);
 196         }
 197     }
 198 
 199     public void characters(char[] ch, int start, int length)
 200             throws SAXException {
 201 
 202         super.characters(ch, start, length);
 203 
 204         try {
 205             if (!isCDATA) {
 206                 eventFactory.setLocation(getCurrentLocation());
 207                 writer.add(eventFactory.createCharacters(new String(ch,
 208                         start, length)));
 209             }
 210 
 211         } catch (XMLStreamException e) {
 212             throw new SAXException(e);
 213         }
 214     }
 215 
 216     public void ignorableWhitespace(char[] ch, int start, int length)
 217             throws SAXException {
 218 
 219         super.ignorableWhitespace(ch, start, length);
 220         characters(ch, start, length);
 221     }
 222 
 223     public void processingInstruction(String target, String data)
 224             throws SAXException {
 225 
 226         if (needToCallStartDocument) {
 227             // Drat. We were trying to postpone this until the first element so that we could get
 228             // the locator, but we can't output a PI before the start document, so we're just
 229             // going to have to do without the locator if it hasn't been set yet.
 230             writeStartDocument();
 231         }
 232 
 233         super.processingInstruction(target, data);
 234         try {
 235             writer.add(eventFactory.createProcessingInstruction(target, data));
 236         } catch (XMLStreamException e) {
 237             throw new SAXException(e);
 238         }
 239     }
 240 
 241     public void endCDATA() throws SAXException {
 242 
 243         eventFactory.setLocation(getCurrentLocation());
 244         try {
 245             writer.add(eventFactory.createCData(CDATABuffer.toString()));
 246         } catch (XMLStreamException e) {
 247             throw new SAXException(e);
 248         }
 249 
 250         super.endCDATA();
 251     }
 252 
 253     @SuppressWarnings({"rawtypes", "unchecked"})
 254     protected void createStartEvents(Attributes attributes, Collection<Attribute>[] events) {
 255 
 256         Map<String, Attribute> nsMap = null;
 257         List<Attribute> attrs = null;
 258 
 259         // create namespaces
 260         if (namespaces != null) {
 261             final int nDecls = namespaces.size();
 262             for (int i = 0; i < nDecls; i++) {
 263                 final String prefix = namespaces.get(i++);
 264                 String uri = namespaces.get(i);
 265                 Namespace ns = createNamespace(prefix, uri);
 266                 if (nsMap == null) {
 267                     nsMap = new HashMap<>();
 268                 }
 269                 nsMap.put(prefix, ns);
 270             }
 271         }
 272 
 273         // create attributes
 274         String[] qname = {null, null};
 275         for (int i = 0, s = attributes.getLength(); i < s; i++) {
 276 
 277             parseQName(attributes.getQName(i), qname);
 278 
 279             String attrPrefix = qname[0];
 280             String attrLocal = qname[1];
 281 
 282             String attrQName = attributes.getQName(i);
 283             String attrValue = attributes.getValue(i);
 284             String attrURI = attributes.getURI(i);
 285 
 286             if ("xmlns".equals(attrQName) || "xmlns".equals(attrPrefix)) {
 287                 // namespace declaration disguised as an attribute. If the
 288                 // namespace has already been declared, skip it, otherwise
 289                 // write it as an namespace
 290                 if (nsMap == null) {
 291                     nsMap = new HashMap<>();
 292                 }
 293 
 294                 if (!nsMap.containsKey(attrLocal)) {
 295                     Namespace ns = createNamespace(attrLocal, attrValue);
 296                     nsMap.put(attrLocal, ns);
 297                 }
 298 
 299             } else {
 300                 Attribute attribute;
 301                 if (attrPrefix.length() > 0) {
 302                     attribute = eventFactory.createAttribute(attrPrefix,
 303                             attrURI, attrLocal, attrValue);
 304                 } else {
 305                     attribute = eventFactory.createAttribute(attrLocal,
 306                             attrValue);
 307                 }
 308 
 309                 if (attrs == null) {
 310                     attrs = new ArrayList<>();
 311                 }
 312                 attrs.add(attribute);
 313             }
 314         }
 315 
 316         events[0] = (nsMap == null ? Collections.EMPTY_LIST : nsMap.values());
 317         events[1] = (attrs == null ? Collections.EMPTY_LIST : attrs);
 318     }
 319 
 320     protected Namespace createNamespace(String prefix, String uri) {
 321         if (prefix == null || prefix.length() == 0) {
 322             return eventFactory.createNamespace(uri);
 323         } else {
 324             return eventFactory.createNamespace(prefix, uri);
 325         }
 326     }
 327 }