View Javadoc

1   /*   Copyright 2004 BEA Systems, Inc.
2    *
3    *   Licensed under the Apache License, Version 2.0 (the "License");
4    *   you may not use this file except in compliance with the License.
5    *   You may obtain a copy of the License at
6    *
7    *       http://www.apache.org/licenses/LICENSE-2.0
8    *
9    *   Unless required by applicable law or agreed to in writing, software
10   *   distributed under the License is distributed on an "AS IS" BASIS,
11   *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   *   See the License for the specific language governing permissions and
13   *   limitations under the License.
14   */
15  package com.bea.xml.stream;
16  
17  import com.bea.xml.stream.util.SymbolTable;
18  import com.bea.xml.stream.util.NamespaceContextImpl;
19  import com.bea.xml.stream.util.Stack;
20  
21  import java.io.Writer;
22  import java.io.OutputStreamWriter;
23  import java.io.IOException;
24  import java.util.HashSet;
25  import java.util.Iterator;
26  import javax.xml.stream.XMLStreamWriter;
27  import javax.xml.stream.XMLStreamException;
28  import javax.xml.stream.XMLOutputFactory;
29  import javax.xml.namespace.NamespaceContext;
30  import java.nio.charset.Charset;
31  import java.nio.charset.CharsetEncoder;
32  
33  /***
34   * <p> The base output class.</p>
35   */
36  
37  public class XMLWriterBase 
38    extends ReaderToWriter
39    implements XMLStreamWriter
40  {
41    protected static final String DEFAULTNS="";
42    private Writer writer;
43    private boolean startElementOpened=false;
44    private boolean isEmpty=false;
45    private ConfigurationContextBase config;
46    private CharsetEncoder encoder;
47    
48    // these two stacks are used to implement the 
49    // writeEndElement() method
50    private Stack localNameStack = new Stack();
51    private Stack prefixStack = new Stack();
52    private Stack uriStack = new Stack();
53    protected NamespaceContextImpl context = new NamespaceContextImpl();
54  
55    private HashSet needToWrite;
56    private boolean isPrefixDefaulting;
57    private int defaultPrefixCount=0;
58    public XMLWriterBase() {}
59    public XMLWriterBase(Writer writer) {
60      this.writer = writer;
61      setWriter(writer);
62    }
63    
64    public void setWriter(Writer writer) {
65      this.writer = writer;
66      setStreamWriter(this);
67      if (writer instanceof OutputStreamWriter) {
68        String charsetName = ((OutputStreamWriter) writer).getEncoding();
69        this.encoder = Charset.forName(charsetName).newEncoder(); 
70      } else {
71        this.encoder = null;
72      }
73    }
74  
75    public void setConfigurationContext(ConfigurationContextBase c) {
76      config = c;
77      isPrefixDefaulting = config.isPrefixDefaulting();
78    }
79  
80    protected void write(String s) 
81      throws XMLStreamException
82    {
83      try {
84        writer.write(s);
85      } catch (IOException e) {
86        throw new XMLStreamException(e);
87      }
88    }
89  
90    protected void write (char c)
91      throws XMLStreamException
92    {
93      try {
94        writer.write(c);
95      } catch (IOException e) {
96        throw new XMLStreamException(e);
97      }
98    }
99  
100   protected void write (char[] c)
101     throws XMLStreamException
102   {
103     try {
104       writer.write(c);
105     } catch (IOException e) {
106       throw new XMLStreamException(e);
107     }
108   }
109 
110   protected void write (char[] c, int start, int len)
111     throws XMLStreamException
112   {
113     try {
114       writer.write(c,start,len);
115     } catch (IOException e) {
116       throw new XMLStreamException(e);
117     }
118   }
119 
120   protected void writeCharactersInternal(char characters[],
121                                          int start,
122                                          int length,
123                                          boolean isAttributeValue) 
124     throws XMLStreamException
125   {
126     if(length == 0) return;
127 
128     // We expect the common case to be that people do not have these
129     // characters in their XML so we make a pass through the char
130     // array and check.  If they're all good, we can call the
131     // underlying Writer once with the entire char array.  If we find
132     // a bad character then we punt it to the slow routine.  So don't
133     // benchmark an XML document that has to be escaped.
134 
135     boolean fastPath = true;
136 
137     for(int i=0, len=length; i<len; i++) {
138       switch(characters[i+start]) {
139       case '&':
140       case '<':
141       case '>':
142       case '\"':
143         fastPath = false;
144         break;
145       }
146       if (encoder != null && !encoder.canEncode(characters[i+start])) {
147         fastPath = false;
148         break;
149       }
150     }
151 
152     if(fastPath) {
153       write(characters,start,length);
154     } else {
155       slowWriteCharacters(characters,start,length, isAttributeValue);
156     }
157   }
158 
159   private void slowWriteCharacters(char chars[],
160                                    int start,
161                                    int length,
162                                    boolean isAttributeValue)
163     throws XMLStreamException
164   {
165     for (int i=0,len=length; i < len; i++) {
166       final char c = chars[i+start];
167       switch (c) {
168       case '&':
169         write("&amp;");
170         break;
171       case '<':
172         write("&lt;");
173         break;
174       case '>':
175         write("&gt;");
176         break;
177       case '\"':
178         if (isAttributeValue) {
179           write("&quot;");
180         } else {
181           write('\"');
182         }
183         break;
184       default:
185         if (encoder != null && !encoder.canEncode(c)) {
186           write("&#");
187           write(Integer.toString(c));
188           write(';');
189         } else {
190           write(c);
191         }
192       }
193     }
194   }
195 
196   protected void closeStartElement() 
197     throws XMLStreamException
198   {
199     if (startElementOpened) {
200       closeStartTag();
201       startElementOpened = false;
202     }
203   }
204 
205   protected boolean isOpen() {
206     return startElementOpened;
207   }
208 
209   protected void closeStartTag() 
210     throws XMLStreamException
211   {
212     flushNamespace();
213     if (isEmpty) {
214       write ("/>");
215       isEmpty = false;
216     }
217     else
218       write(">");
219   }
220 
221   private void openStartElement() 
222     throws XMLStreamException
223   {
224     if (startElementOpened) 
225       closeStartTag();
226     else
227       startElementOpened = true;
228   }
229 
230   
231   protected String writeName(String prefix,String namespaceURI, String localName) 
232     throws XMLStreamException
233   {
234     if (!("".equals(namespaceURI)))
235       prefix = getPrefixInternal(namespaceURI);
236     if (!("".equals(prefix))) {
237       write(prefix);
238       write(":");
239     }
240     write(localName);
241     return prefix;
242 
243   }
244 
245   private String getPrefixInternal(String namespaceURI) {
246     String prefix = context.getPrefix(namespaceURI);
247     if (prefix == null) {
248       return "";
249     }
250     return prefix;
251   }
252   protected String getURIInternal(String prefix) {
253     String uri = context.getNamespaceURI(prefix);
254     if (uri == null) {
255       return "";
256     }
257     return uri;
258   }
259 
260 
261   protected void openStartTag() 
262     throws XMLStreamException
263   {
264     write("<");
265   }
266 
267   private void needToWrite(String uri) {
268     if (needToWrite == null) {
269       needToWrite = new HashSet();
270     }
271     needToWrite.add(uri);
272   }
273 
274   private void prepareNamespace(String uri)
275     throws XMLStreamException
276   {
277     if (!isPrefixDefaulting) return;
278     if ("".equals(uri)) return;
279     String prefix = getPrefix(uri);
280     // if the prefix is bound then we can ignore and return
281     if (prefix != null) return;
282 
283     defaultPrefixCount++;
284     prefix = "ns"+defaultPrefixCount;
285     setPrefix(prefix,uri);
286   }   
287 
288   private void removeNamespace(String uri) {
289     if (!isPrefixDefaulting) return;
290     needToWrite.remove(uri);
291   }
292 
293   private void flushNamespace() 
294     throws XMLStreamException
295   {
296     if (!isPrefixDefaulting) return;
297     Iterator i = needToWrite.iterator();
298     while (i.hasNext()) {
299       String uri = (String) i.next();
300       String prefix = context.getPrefix(uri);
301       if (prefix == null) {
302         throw new XMLStreamException("Unable to default prefix with uri:"+
303                                      uri);
304       }
305       writeNamespace(prefix,uri);
306     }
307     needToWrite.clear();
308   }
309 
310 
311   protected void writeStartElementInternal(String namespaceURI, String localName) 
312     throws XMLStreamException 
313   {
314     if (namespaceURI == null)
315       throw new IllegalArgumentException("The namespace URI may not be null");
316     if (localName == null)
317       throw new IllegalArgumentException("The local name  may not be null");
318 
319     openStartElement();
320     openStartTag();
321     prepareNamespace(namespaceURI);
322     prefixStack.push(writeName("",namespaceURI, localName));
323     localNameStack.push(localName);
324     uriStack.push(namespaceURI);
325   }
326 
327   public void writeStartElement(String namespaceURI, String localName) 
328     throws XMLStreamException 
329   {
330     context.openScope();
331     writeStartElementInternal(namespaceURI,localName);
332   }
333 
334 
335 
336   public void writeStartElement(String prefix,
337                                 String localName,
338                                 String namespaceURI) 
339     throws XMLStreamException 
340   {
341     if (namespaceURI == null)
342       throw new IllegalArgumentException("The namespace URI may not be null");
343     if (localName == null)
344       throw new IllegalArgumentException("The local name may not be null");
345     if (prefix == null)
346       throw new IllegalArgumentException("The prefix may not be null");
347     context.openScope();
348     prepareNamespace(namespaceURI);
349     context.bindNamespace(prefix,namespaceURI);
350     writeStartElementInternal(namespaceURI,localName);
351   }
352 
353   public void writeStartElement(String localName) 
354     throws XMLStreamException
355   {
356     context.openScope();
357     writeStartElement("",localName);
358   }
359 
360   public void writeEmptyElement(String namespaceURI, String localName) 
361     throws XMLStreamException 
362   {
363     openStartElement();
364     prepareNamespace(namespaceURI);
365     isEmpty = true;
366     write("<");
367     writeName("",namespaceURI,localName);
368   }
369 
370   public void writeEmptyElement(String prefix, 
371                                 String localName,
372                                 String namespaceURI) 
373     throws XMLStreamException 
374   {
375     openStartElement();
376     prepareNamespace(namespaceURI);
377     isEmpty = true;
378     write("<");
379     write(prefix);
380     write(":");
381     write(localName);
382   }
383 
384   public void writeEmptyElement(String localName) 
385     throws XMLStreamException 
386   {
387     writeEmptyElement("",localName);
388   }
389 
390   protected void openEndTag() 
391     throws XMLStreamException
392   {
393     write("</");
394   }
395   protected void closeEndTag() 
396     throws XMLStreamException
397   {
398     write(">");
399   }
400   public void writeEndElement() 
401     throws XMLStreamException 
402   {
403     closeStartElement();
404     String prefix = (String) prefixStack.pop();
405     String local = (String) localNameStack.pop();
406     uriStack.pop();
407     openEndTag();
408     writeName(prefix,"",local);
409     closeEndTag();
410     context.closeScope();
411   }
412 
413   public void writeRaw(String data) 
414     throws XMLStreamException 
415   {
416     closeStartElement();
417     write(data);
418   }
419 
420   public void close() throws XMLStreamException {
421     flush();
422   }
423   public void flush() throws XMLStreamException {
424     try {
425       writer.flush();
426     } catch (IOException e) {
427       throw new XMLStreamException(e);
428     }
429   }
430 
431   public void writeEndDocument() 
432     throws XMLStreamException 
433   {
434     while(!localNameStack.isEmpty())
435       writeEndElement();
436   }
437 
438   public void writeAttribute(String localName, String value) 
439     throws XMLStreamException
440   {
441     writeAttribute("",localName,value);
442   }
443   public void writeAttribute(String namespaceURI,
444                              String localName,
445                              String value) 
446     throws XMLStreamException
447   {
448     if (!isOpen())
449       throw new XMLStreamException("A start element must be written before an attribute");
450     prepareNamespace(namespaceURI);
451     write(" ");
452     writeName("",namespaceURI,localName);
453     write("=\"");
454     writeCharactersInternal(value.toCharArray(),0,value.length(),true);
455     write("\"");
456   }
457 
458   public void writeAttribute(String prefix,
459                              String namespaceURI,
460                              String localName,
461                              String value) 
462     throws XMLStreamException
463   {
464     if (!isOpen())
465       throw new XMLStreamException("A start element must be written before an attribute");
466     prepareNamespace(namespaceURI);
467     context.bindNamespace(prefix,namespaceURI);
468     write(" ");
469     writeName(prefix,namespaceURI,localName);
470     write("=\"");
471     writeCharactersInternal(value.toCharArray(),0,value.length(),true);
472     write("\"");
473   }
474 
475   public void writeNamespace(String prefix, String namespaceURI) 
476     throws XMLStreamException 
477   {
478     if(!isOpen())
479      throw new XMLStreamException("A start element must be written before a namespace");
480     if (prefix == null || "".equals(prefix) || "xmlns".equals(prefix)) {
481       writeDefaultNamespace(namespaceURI);
482       return;
483     }
484     write(" xmlns:");
485     write(prefix);
486     write("=\"");
487     write(namespaceURI);
488     write("\"");
489     setPrefix(prefix,namespaceURI);
490   }
491 
492   public void writeDefaultNamespace(String namespaceURI)
493     throws XMLStreamException 
494   {
495     if(!isOpen())
496      throw new XMLStreamException("A start element must be written before the default namespace");
497     write(" xmlns");
498     write("=\"");
499     write(namespaceURI);
500     write("\"");
501     setPrefix(DEFAULTNS,namespaceURI);
502   }
503 
504   public void writeComment(String data) 
505     throws XMLStreamException
506   {
507     closeStartElement();
508     write("<!--");
509     if (data != null)
510       write(data);
511     write("-->");
512   }
513 
514   public void writeProcessingInstruction(String target) 
515     throws XMLStreamException
516   {
517     closeStartElement();
518     writeProcessingInstruction(target,null);
519   }
520 
521   public void writeProcessingInstruction(String target,
522                                          String text) 
523     throws XMLStreamException
524   {
525     closeStartElement();
526     write("<?");
527     if (target != null)
528       write(target);
529     if (text != null) {
530       write(text);
531     }
532     write("?>");
533   }
534 
535   public void writeDTD(String dtd) 
536     throws XMLStreamException
537   {
538     write(dtd);
539   }
540   public void writeCData(String data) 
541     throws XMLStreamException
542   {
543     closeStartElement();
544     write("<![CDATA[");
545     if (data != null)
546       write(data);
547     write("]]>");
548   }
549 
550   public void writeEntityRef(String name) 
551     throws XMLStreamException
552   {
553     closeStartElement();
554     write("&");
555     write(name);
556     write(";");
557   }
558 
559   public void writeStartDocument() 
560     throws XMLStreamException
561   {
562     write("<?xml version='1.0' encoding='utf-8'?>");
563   }
564 
565   public void writeStartDocument(String version) 
566     throws XMLStreamException
567   {
568     write("<?xml version='");
569     write(version);
570     write("'?>");
571   }
572 
573   public void writeStartDocument(String encoding,
574                                  String version) 
575     throws XMLStreamException
576   {
577     write("<?xml version='");
578     write(version);
579     write("' encoding='");
580     write(encoding);
581     write("'?>");
582   }
583 
584   public void writeCharacters(String text) 
585     throws XMLStreamException
586   {
587     closeStartElement();
588     writeCharactersInternal(text.toCharArray(),0,text.length(),false);
589   }
590 
591   public void writeCharacters(char[] text, int start, int len) 
592     throws XMLStreamException
593   {
594     closeStartElement();
595     writeCharactersInternal(text,start,len,false);
596   }
597 
598   public String getPrefix(String uri) 
599     throws XMLStreamException
600   {
601     return context.getPrefix(uri);
602   }
603 
604   public void setPrefix(String prefix, String uri) 
605     throws XMLStreamException
606   {
607     needToWrite(uri);
608     context.bindNamespace(prefix,uri);
609   }
610 
611   public void setDefaultNamespace(String uri) 
612     throws XMLStreamException
613   {
614     needToWrite(uri);
615     context.bindDefaultNameSpace(uri);
616   }
617 
618   public void setNamespaceContext(NamespaceContext context)
619     throws XMLStreamException 
620   {
621     if (context == null) throw new NullPointerException("The namespace "+
622                                                         " context may"+
623                                                         " not be null.");
624     this.context = new NamespaceContextImpl(context);
625   }
626 
627   public NamespaceContext getNamespaceContext() {
628     return context;
629   }
630 
631   public Object getProperty(String name)
632     throws IllegalArgumentException
633   {
634     return config.getProperty(name);
635   }
636 
637   public static void main(String args[]) throws Exception {
638 
639     /********
640     Writer w = new java.io.OutputStreamWriter(System.out);
641     XMLWriterBase writer = 
642       new XMLWriterBase(w);
643     writer.writeStartDocument();
644     writer.setPrefix("c","http://c");
645     writer.setDefaultNamespace("http://c");
646     writer.writeStartElement("http://c","a");
647     writer.writeAttribute("b","blah");
648     writer.writeNamespace("c","http://c");
649     writer.writeDefaultNamespace("http://c");
650     writer.setPrefix("d","http://c");
651     writer.writeEmptyElement("http://c","d");
652     writer.writeAttribute("http://c","chris","fry");
653     writer.writeNamespace("d","http://c");
654     writer.writeCharacters("foo bar foo");
655     writer.writeEndElement();
656     writer.flush();
657     ********/
658     XMLOutputFactory output = XMLOutputFactoryBase.newInstance();
659     output.setProperty(javax.xml.stream.XMLOutputFactory.IS_REPAIRING_NAMESPACES,new Boolean(true));
660     Writer myWriter = new java.io.OutputStreamWriter(
661       new java.io.FileOutputStream("tmp"),"us-ascii");
662     XMLStreamWriter writer2 = output.createXMLStreamWriter(myWriter);
663     writer2.writeStartDocument();
664     writer2.setPrefix("c","http://c");
665     writer2.setDefaultNamespace("http://d");
666     writer2.writeStartElement("http://c","a");
667     writer2.writeAttribute("b","blah");
668     writer2.writeEmptyElement("http://c","d");
669     writer2.writeEmptyElement("http://d","e");
670     writer2.writeEmptyElement("http://e","f");
671     writer2.writeEmptyElement("http://f","g");
672     writer2.writeAttribute("http://c","chris","fry");
673     writer2.writeCharacters("foo bar foo");
674     writer2.writeCharacters("bad char coming[");
675     char c = 0x1024;
676     char[] array = new char[1]; 
677     array[0]=c;
678     writer2.writeCharacters(new String(array));
679     writer2.writeCharacters("]");
680     writer2.writeEndElement();
681     writer2.flush();
682     
683     
684   }
685 }