1 /* XMLStreamWriterImpl.java --
2    Copyright (C) 2005  Free Software Foundation, Inc.
3 
4 This file is part of GNU Classpath.
5 
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
10 
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 General Public License for more details.
15 
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING.  If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
20 
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library.  Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
25 
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module.  An independent module is a module which is not derived from
33 or based on this library.  If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so.  If you do not wish to do so, delete this
36 exception statement from your version. */
37 
38 package gnu.xml.stream;
39 
40 import java.io.IOException;
41 import java.io.Writer;
42 import java.util.Enumeration;
43 import java.util.HashSet;
44 import java.util.LinkedList;
45 import java.util.Set;
46 
47 import javax.xml.XMLConstants;
48 import javax.xml.namespace.NamespaceContext;
49 import javax.xml.stream.XMLStreamException;
50 import javax.xml.stream.XMLStreamWriter;
51 
52 import org.xml.sax.helpers.NamespaceSupport;
53 
54 /**
55  * Simple XML stream writer.
56  *
57  * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a>
58  */
59 public class XMLStreamWriterImpl
60   implements XMLStreamWriter
61 {
62 
63   /**
64    * The underlying character stream to write to.
65    */
66   protected final Writer writer;
67 
68   /**
69    * The encoding being used.
70    * Note that this must match the encoding of the character stream.
71    */
72   protected final String encoding;
73 
74   /**
75    * Whether prefix defaulting is being used.
76    * If true and a prefix has not been defined for a namespace specified on
77    * an element or an attribute, a new prefix and namespace declaration will
78    * be created.
79    */
80   protected final boolean prefixDefaulting;
81 
82   /**
83    * The namespace context used to determine the namespace-prefix mappings
84    * in scope.
85    */
86   protected NamespaceContext namespaceContext;
87 
88   /**
89    * The stack of elements in scope.
90    * Used to close the remaining elements.
91    */
92   private LinkedList elements;
93 
94   /**
95    * Whether a start element has been opened but not yet closed.
96    */
97   private boolean inStartElement;
98 
99   /**
100    * Whether we are in an empty element.
101    */
102   private boolean emptyElement;
103 
104   private NamespaceSupport namespaces;
105   private int count = 0;
106 
107   private boolean xml11;
108   private boolean hasXML11RestrictedChars;
109 
110   /**
111    * Constructor.
112    * @see #writer
113    * @see #encoding
114    * @see #prefixDefaulting
115    */
XMLStreamWriterImpl(Writer writer, String encoding, boolean prefixDefaulting)116   protected XMLStreamWriterImpl(Writer writer, String encoding,
117                                 boolean prefixDefaulting)
118   {
119     this.writer = writer;
120     this.encoding = encoding;
121     this.prefixDefaulting = prefixDefaulting;
122     elements = new LinkedList();
123     namespaces = new NamespaceSupport();
124   }
125 
126   /**
127    * Write the end of a start-element event.
128    * This will close the element if it was defined to be an empty element.
129    */
endStartElement()130   private void endStartElement()
131     throws IOException
132   {
133     if (!inStartElement)
134       return;
135     if (emptyElement)
136       {
137         writer.write('/');
138         elements.removeLast();
139         namespaces.popContext();
140         emptyElement = false;
141       }
142     writer.write('>');
143     inStartElement = false;
144   }
145 
writeStartElement(String localName)146   public void writeStartElement(String localName)
147     throws XMLStreamException
148   {
149     try
150       {
151         if (!isName(localName))
152           throw new IllegalArgumentException("illegal Name: " + localName);
153 
154         endStartElement();
155         namespaces.pushContext();
156 
157         writer.write('<');
158         writer.write(localName);
159 
160         elements.addLast(new String[] { null, localName });
161         inStartElement = true;
162       }
163     catch (IOException e)
164       {
165         XMLStreamException e2 = new XMLStreamException(e);
166         e2.initCause(e);
167         throw e2;
168       }
169   }
170 
writeStartElement(String namespaceURI, String localName)171   public void writeStartElement(String namespaceURI, String localName)
172     throws XMLStreamException
173   {
174     try
175       {
176         if (namespaceURI != null && !isURI(namespaceURI))
177           throw new IllegalArgumentException("illegal URI: " + namespaceURI);
178         if (!isName(localName))
179           throw new IllegalArgumentException("illegal Name: " + localName);
180 
181         endStartElement();
182         namespaces.pushContext();
183 
184         String prefix = getPrefix(namespaceURI);
185         boolean isDeclared = (prefix != null);
186         if (!isDeclared)
187           {
188             if (prefixDefaulting)
189               prefix = createPrefix(namespaceURI);
190             else
191               throw new XMLStreamException("namespace " + namespaceURI +
192                                            " has not been declared");
193           }
194         writer.write('<');
195         if (!"".equals(prefix))
196           {
197             writer.write(prefix);
198             writer.write(':');
199           }
200         writer.write(localName);
201         inStartElement = true;
202         if (!isDeclared)
203           {
204             writeNamespaceImpl(prefix, namespaceURI);
205           }
206 
207         elements.addLast(new String[] { prefix, localName });
208       }
209     catch (IOException e)
210       {
211         XMLStreamException e2 = new XMLStreamException(e);
212         e2.initCause(e);
213         throw e2;
214       }
215   }
216 
217   /**
218    * Creates a new unique prefix in the document.
219    * Subclasses may override this method to provide a suitably unique prefix
220    * for the given namespace.
221    * @param namespaceURI the namespace URI
222    */
createPrefix(String namespaceURI)223   protected String createPrefix(String namespaceURI)
224   {
225     Set prefixes = new HashSet();
226     for (Enumeration e = namespaces.getPrefixes(); e.hasMoreElements(); )
227       prefixes.add(e.nextElement());
228     String ret;
229     do
230       {
231         ret = "ns" + (count++);
232       }
233     while (prefixes.contains(ret));
234     return ret;
235   }
236 
writeStartElement(String prefix, String localName, String namespaceURI)237   public void writeStartElement(String prefix, String localName,
238                                 String namespaceURI)
239     throws XMLStreamException
240   {
241     try
242       {
243         if (namespaceURI != null && !isURI(namespaceURI))
244           throw new IllegalArgumentException("illegal URI: " + namespaceURI);
245         if (prefix != null && !isPrefix(prefix))
246           throw new IllegalArgumentException("illegal NCName: " + prefix);
247         if (!isNCName(localName))
248           throw new IllegalArgumentException("illegal NCName: " + localName);
249 
250         endStartElement();
251         namespaces.pushContext();
252 
253         String currentPrefix = getPrefix(namespaceURI);
254         boolean isCurrent = prefix.equals(currentPrefix);
255         writer.write('<');
256         if (!"".equals(prefix))
257           {
258             writer.write(prefix);
259             writer.write(':');
260           }
261         writer.write(localName);
262         if (prefixDefaulting && !isCurrent)
263           {
264             writeNamespaceImpl(prefix, namespaceURI);
265           }
266 
267         elements.addLast(new String[] { prefix, localName });
268         inStartElement = true;
269       }
270     catch (IOException e)
271       {
272         XMLStreamException e2 = new XMLStreamException(e);
273         e2.initCause(e);
274         throw e2;
275       }
276   }
277 
writeEmptyElement(String namespaceURI, String localName)278   public void writeEmptyElement(String namespaceURI, String localName)
279     throws XMLStreamException
280   {
281     writeStartElement(namespaceURI, localName);
282     emptyElement = true;
283   }
284 
writeEmptyElement(String prefix, String localName, String namespaceURI)285   public void writeEmptyElement(String prefix, String localName,
286                                 String namespaceURI)
287     throws XMLStreamException
288   {
289     writeStartElement(prefix, localName, namespaceURI);
290     emptyElement = true;
291   }
292 
writeEmptyElement(String localName)293   public void writeEmptyElement(String localName)
294     throws XMLStreamException
295   {
296     writeStartElement(localName);
297     emptyElement = true;
298   }
299 
writeEndElement()300   public void writeEndElement()
301     throws XMLStreamException
302   {
303     if (elements.isEmpty())
304       throw new IllegalStateException("no matching start element");
305     try
306       {
307         endStartElement();
308         String[] element = (String[]) elements.removeLast();
309         namespaces.popContext();
310 
311         writer.write('<');
312         writer.write('/');
313         if (element[0] != null && !"".equals(element[0]))
314           {
315             writer.write(element[0]);
316             writer.write(':');
317           }
318         writer.write(element[1]);
319         writer.write('>');
320       }
321     catch (IOException e)
322       {
323         XMLStreamException e2 = new XMLStreamException(e);
324         e2.initCause(e);
325         throw e2;
326       }
327   }
328 
writeEndDocument()329   public void writeEndDocument()
330     throws XMLStreamException
331   {
332     while (!elements.isEmpty())
333       writeEndElement();
334   }
335 
close()336   public void close()
337     throws XMLStreamException
338   {
339     flush();
340   }
341 
flush()342   public void flush()
343     throws XMLStreamException
344   {
345     try
346       {
347         writer.flush();
348       }
349     catch (IOException e)
350       {
351         XMLStreamException e2 = new XMLStreamException(e);
352         e2.initCause(e);
353         throw e2;
354       }
355   }
356 
writeAttribute(String localName, String value)357   public void writeAttribute(String localName, String value)
358     throws XMLStreamException
359   {
360     if (!inStartElement)
361       throw new IllegalStateException();
362     try
363       {
364         if (!isName(localName))
365           throw new IllegalArgumentException("illegal Name: " + localName);
366         if (!isChars(value))
367           throw new IllegalArgumentException("illegal character: " + value);
368 
369         writer.write(' ');
370         writer.write(localName);
371         writer.write('=');
372         writer.write('"');
373         if (hasXML11RestrictedChars)
374           writeEncodedWithRestrictedChars(value, true);
375         else
376           writeEncoded(value, true);
377         writer.write('"');
378       }
379     catch (IOException e)
380       {
381         XMLStreamException e2 = new XMLStreamException(e);
382         e2.initCause(e);
383         throw e2;
384       }
385   }
386 
writeAttribute(String prefix, String namespaceURI, String localName, String value)387   public void writeAttribute(String prefix, String namespaceURI,
388                              String localName, String value)
389     throws XMLStreamException
390   {
391     if (!inStartElement)
392       throw new IllegalStateException();
393     try
394       {
395         if (namespaceURI != null && !isURI(namespaceURI))
396           throw new IllegalArgumentException("illegal URI: " + namespaceURI);
397         if (prefix != null && !isPrefix(prefix))
398           throw new IllegalArgumentException("illegal NCName: " + prefix);
399         if (!isNCName(localName))
400           throw new IllegalArgumentException("illegal NCName: " + localName);
401         if (!isChars(value))
402           throw new IllegalArgumentException("illegal character: " + value);
403 
404         String currentPrefix = getPrefix(namespaceURI);
405         if (currentPrefix == null)
406           {
407             if (prefixDefaulting)
408               writeNamespaceImpl(prefix, namespaceURI);
409             else
410               throw new XMLStreamException("namespace " + namespaceURI +
411                                            " is not bound");
412           }
413         else if (!currentPrefix.equals(prefix))
414           throw new XMLStreamException("namespace " + namespaceURI +
415                                        " is bound to prefix " +
416                                        currentPrefix);
417         writer.write(' ');
418         if (!"".equals(prefix))
419           {
420             writer.write(prefix);
421             writer.write(':');
422           }
423         writer.write(localName);
424         writer.write('=');
425         writer.write('"');
426         if (hasXML11RestrictedChars)
427           writeEncodedWithRestrictedChars(value, true);
428         else
429           writeEncoded(value, true);
430         writer.write('"');
431       }
432     catch (IOException e)
433       {
434         XMLStreamException e2 = new XMLStreamException(e);
435         e2.initCause(e);
436         throw e2;
437       }
438   }
439 
writeAttribute(String namespaceURI, String localName, String value)440   public void writeAttribute(String namespaceURI, String localName,
441                              String value)
442     throws XMLStreamException
443   {
444     if (!inStartElement)
445       throw new IllegalStateException();
446     try
447       {
448         if (namespaceURI != null && !isURI(namespaceURI))
449           throw new IllegalArgumentException("illegal URI: " + namespaceURI);
450         if (!isName(localName))
451           throw new IllegalArgumentException("illegal Name: " + localName);
452         if (!isChars(value))
453           throw new IllegalArgumentException("illegal character: " + value);
454 
455         String prefix = getPrefix(namespaceURI);
456         if (prefix == null)
457           {
458             if (prefixDefaulting)
459               {
460                 prefix = XMLConstants.DEFAULT_NS_PREFIX;
461                 writeNamespaceImpl(prefix, namespaceURI);
462               }
463             else
464               throw new XMLStreamException("namespace " + namespaceURI +
465                                            " is not bound");
466           }
467         writer.write(' ');
468         if (!"".equals(prefix))
469           {
470             writer.write(prefix);
471             writer.write(':');
472           }
473         writer.write(localName);
474         writer.write('=');
475         writer.write('"');
476         if (hasXML11RestrictedChars)
477           writeEncodedWithRestrictedChars(value, true);
478         else
479           writeEncoded(value, true);
480         writer.write('"');
481       }
482     catch (IOException e)
483       {
484         XMLStreamException e2 = new XMLStreamException(e);
485         e2.initCause(e);
486         throw e2;
487       }
488   }
489 
writeNamespace(String prefix, String namespaceURI)490   public void writeNamespace(String prefix, String namespaceURI)
491     throws XMLStreamException
492   {
493     if (prefix == null || "".equals(prefix) || "xmlns".equals(prefix))
494     {
495       writeDefaultNamespace(namespaceURI);
496       return;
497     }
498     if (!inStartElement)
499       throw new IllegalStateException();
500     try
501       {
502         if (!isURI(namespaceURI))
503           throw new IllegalArgumentException("illegal URI: " + namespaceURI);
504         if (!isPrefix(prefix))
505           throw new IllegalArgumentException("illegal NCName: " + prefix);
506       }
507     catch (IOException e)
508       {
509         XMLStreamException e2 = new XMLStreamException(e);
510         e2.initCause(e);
511         throw e2;
512       }
513     writeNamespaceImpl(prefix, namespaceURI);
514   }
515 
writeNamespaceImpl(String prefix, String namespaceURI)516   private void writeNamespaceImpl(String prefix, String namespaceURI)
517     throws XMLStreamException
518   {
519     try
520       {
521         if (prefix == null)
522           prefix = XMLConstants.DEFAULT_NS_PREFIX;
523 
524         setPrefix(prefix, namespaceURI);
525 
526         writer.write(' ');
527         writer.write("xmlns");
528         if (!XMLConstants.DEFAULT_NS_PREFIX.equals(prefix))
529           {
530             writer.write(':');
531             writer.write(prefix);
532           }
533         writer.write('=');
534         writer.write('"');
535         writer.write(namespaceURI);
536         writer.write('"');
537       }
538     catch (IOException e)
539       {
540         XMLStreamException e2 = new XMLStreamException(e);
541         e2.initCause(e);
542         throw e2;
543       }
544   }
545 
writeDefaultNamespace(String namespaceURI)546   public void writeDefaultNamespace(String namespaceURI)
547     throws XMLStreamException
548   {
549     if (!inStartElement)
550       throw new IllegalStateException();
551     if (!isURI(namespaceURI))
552       throw new IllegalArgumentException("illegal URI: " + namespaceURI);
553     writeNamespaceImpl(XMLConstants.DEFAULT_NS_PREFIX, namespaceURI);
554   }
555 
writeComment(String data)556   public void writeComment(String data)
557     throws XMLStreamException
558   {
559     if (data == null)
560       return;
561     try
562       {
563         if (!isChars(data))
564           throw new IllegalArgumentException("illegal XML character: " + data);
565         if (data.indexOf("--") != -1)
566           throw new IllegalArgumentException("illegal comment: " + data);
567 
568         endStartElement();
569 
570         writer.write("<!--");
571         if (hasXML11RestrictedChars)
572           {
573             int[] seq = UnicodeReader.toCodePointArray(data);
574             for (int i = 0; i < seq.length; i++)
575               {
576                 int c = seq[i];
577                 if (XMLParser.isXML11RestrictedChar(c))
578                   writer.write("&#x" + Integer.toHexString(c) + ";");
579                 else
580                   writer.write(Character.toChars(i));
581               }
582           }
583         else
584           writer.write(data);
585         writer.write("-->");
586       }
587     catch (IOException e)
588       {
589         XMLStreamException e2 = new XMLStreamException(e);
590         e2.initCause(e);
591         throw e2;
592       }
593   }
594 
writeProcessingInstruction(String target)595   public void writeProcessingInstruction(String target)
596     throws XMLStreamException
597   {
598     writeProcessingInstruction(target, null);
599   }
600 
writeProcessingInstruction(String target, String data)601   public void writeProcessingInstruction(String target, String data)
602     throws XMLStreamException
603   {
604     try
605       {
606         if (!isName(target) || "xml".equalsIgnoreCase(target))
607           throw new IllegalArgumentException("illegal PITarget: " + target);
608         if (data != null && !isChars(data))
609           throw new IllegalArgumentException("illegal XML character: " + data);
610 
611         endStartElement();
612 
613         writer.write('<');
614         writer.write('?');
615         writer.write(target);
616         if (data != null)
617           {
618             writer.write(' ');
619             if (hasXML11RestrictedChars)
620               {
621                 int[] seq = UnicodeReader.toCodePointArray(data);
622                 for (int i = 0; i < seq.length; i++)
623                   {
624                     int c = seq[i];
625                     if (XMLParser.isXML11RestrictedChar(c))
626                       writer.write("&#x" + Integer.toHexString(c) + ";");
627                     else
628                       writer.write(Character.toChars(i));
629                   }
630               }
631             else
632               writer.write(data);
633           }
634         writer.write('?');
635         writer.write('>');
636       }
637     catch (IOException e)
638       {
639         XMLStreamException e2 = new XMLStreamException(e);
640         e2.initCause(e);
641         throw e2;
642       }
643   }
644 
writeCData(String data)645   public void writeCData(String data)
646     throws XMLStreamException
647   {
648     try
649       {
650         if (!isChars(data) || hasXML11RestrictedChars)
651           throw new IllegalArgumentException("illegal XML character: " + data);
652         if (data.indexOf("]]") != -1)
653           throw new IllegalArgumentException("illegal CDATA section: " + data);
654 
655         endStartElement();
656 
657         writer.write("<![CDATA[");
658         writer.write(data);
659         writer.write("]]>");
660       }
661     catch (IOException e)
662       {
663         XMLStreamException e2 = new XMLStreamException(e);
664         e2.initCause(e);
665         throw e2;
666       }
667   }
668 
writeDTD(String dtd)669   public void writeDTD(String dtd)
670     throws XMLStreamException
671   {
672     try
673       {
674         // XXX: Should we parse the doctypedecl at this point to ensure
675         // wellformedness?
676         writer.write("<!DOCTYPE ");
677         writer.write(dtd);
678         writer.write('>');
679       }
680     catch (IOException e)
681       {
682         XMLStreamException e2 = new XMLStreamException(e);
683         e2.initCause(e);
684         throw e2;
685       }
686   }
687 
writeEntityRef(String name)688   public void writeEntityRef(String name)
689     throws XMLStreamException
690   {
691     try
692       {
693         if (!isName(name))
694           throw new IllegalArgumentException("illegal Name: " + name);
695 
696         endStartElement();
697 
698         writer.write('&');
699         writer.write(name);
700         writer.write(';');
701       }
702     catch (IOException e)
703       {
704         XMLStreamException e2 = new XMLStreamException(e);
705         e2.initCause(e);
706         throw e2;
707       }
708   }
709 
writeStartDocument()710   public void writeStartDocument()
711     throws XMLStreamException
712   {
713     writeStartDocument(null, null);
714   }
715 
writeStartDocument(String version)716   public void writeStartDocument(String version)
717     throws XMLStreamException
718   {
719     writeStartDocument(null, version);
720   }
721 
writeStartDocument(String encoding, String version)722   public void writeStartDocument(String encoding, String version)
723     throws XMLStreamException
724   {
725     if (version == null)
726       version = "1.0";
727     else if ("1.1".equals(version))
728       xml11 = true;
729     encoding = this.encoding; // YES: the parameter must be ignored
730     if (encoding == null)
731       encoding = "UTF-8";
732     if (!"1.0".equals(version) && !"1.1".equals(version))
733       throw new IllegalArgumentException(version);
734     try
735       {
736         writer.write("<?xml version=\"");
737         writer.write(version);
738         writer.write("\" encoding=\"");
739         writer.write(encoding);
740         writer.write("\"?>");
741         writer.write(System.getProperty("line.separator"));
742       }
743     catch (IOException e)
744       {
745         XMLStreamException e2 = new XMLStreamException(e);
746         e2.initCause(e);
747         throw e2;
748       }
749   }
750 
writeCharacters(String text)751   public void writeCharacters(String text)
752     throws XMLStreamException
753   {
754     if (text == null)
755       return;
756     try
757       {
758         if (!isChars(text))
759           throw new IllegalArgumentException("illegal XML character: " + text);
760 
761         endStartElement();
762 
763         if (hasXML11RestrictedChars)
764           writeEncodedWithRestrictedChars(text, false);
765         else
766           writeEncoded(text, false);
767       }
768     catch (IOException e)
769       {
770         XMLStreamException e2 = new XMLStreamException(e);
771         e2.initCause(e);
772         throw e2;
773       }
774   }
775 
writeCharacters(char[] text, int start, int len)776   public void writeCharacters(char[] text, int start, int len)
777     throws XMLStreamException
778   {
779     writeCharacters(new String(text, start, len));
780   }
781 
getPrefix(String uri)782   public String getPrefix(String uri)
783     throws XMLStreamException
784   {
785     String prefix = namespaces.getPrefix(uri);
786     if (prefix == null && namespaceContext != null)
787       prefix = namespaceContext.getPrefix(uri);
788     return prefix;
789   }
790 
setPrefix(String prefix, String uri)791   public void setPrefix(String prefix, String uri)
792     throws XMLStreamException
793   {
794     try
795       {
796         if (!isURI(uri))
797           throw new IllegalArgumentException("illegal URI: " + uri);
798         if (!isPrefix(prefix))
799           throw new IllegalArgumentException("illegal NCName: " + prefix);
800       }
801     catch (IOException e)
802       {
803         XMLStreamException e2 = new XMLStreamException(e);
804         e2.initCause(e);
805         throw e2;
806       }
807     if (!namespaces.declarePrefix(prefix, uri))
808       throw new XMLStreamException("illegal prefix " + prefix);
809   }
810 
setDefaultNamespace(String uri)811   public void setDefaultNamespace(String uri)
812     throws XMLStreamException
813   {
814     if (!isURI(uri))
815       throw new IllegalArgumentException("illegal URI: " + uri);
816     if (!namespaces.declarePrefix(XMLConstants.DEFAULT_NS_PREFIX, uri))
817       throw new XMLStreamException("illegal default namespace prefix");
818   }
819 
setNamespaceContext(NamespaceContext context)820   public void setNamespaceContext(NamespaceContext context)
821     throws XMLStreamException
822   {
823     namespaceContext = context;
824   }
825 
getNamespaceContext()826   public NamespaceContext getNamespaceContext()
827   {
828     return namespaceContext;
829   }
830 
getProperty(String name)831   public Object getProperty(String name)
832     throws IllegalArgumentException
833   {
834     throw new IllegalArgumentException(name);
835   }
836 
837   /**
838    * Write the specified text, ensuring that the content is suitably encoded
839    * for XML.
840    * @param text the text to write
841    * @param inAttr whether we are in an attribute value
842    */
writeEncoded(String text, boolean inAttr)843   private void writeEncoded(String text, boolean inAttr)
844     throws IOException
845   {
846     char[] chars = text.toCharArray();
847     int start = 0;
848     int end = chars.length;
849     int len = 0;
850     for (int i = start; i < end; i++)
851       {
852         char c = chars[i];
853         if (c == '<' || c == '>' || c == '&')
854           {
855             writer.write(chars, start, len);
856             if (c == '<')
857               writer.write("&lt;");
858             else if (c == '>')
859               writer.write("&gt;");
860             else
861               writer.write("&amp;");
862             start = i + 1;
863             len = 0;
864           }
865         else if (inAttr && (c == '"' || c == '\''))
866           {
867             writer.write(chars, start, len);
868             if (c == '"')
869               writer.write("&quot;");
870             else
871               writer.write("&apos;");
872             start = i + 1;
873             len = 0;
874           }
875         else
876           len++;
877       }
878     if (len > 0)
879       writer.write(chars, start, len);
880   }
881 
882   /**
883    * Writes the specified text, in the knowledge that some of the
884    * characters are XML 1.1 restricted characters.
885    */
writeEncodedWithRestrictedChars(String text, boolean inAttr)886   private void writeEncodedWithRestrictedChars(String text, boolean inAttr)
887     throws IOException
888   {
889     int[] seq = UnicodeReader.toCodePointArray(text);
890     for (int i = 0; i < seq.length; i++)
891       {
892         int c = seq[i];
893         switch (c)
894           {
895           case 0x3c: // '<'
896             writer.write("&lt;");
897             break;
898           case 0x3e: // '>'
899             writer.write("&gt;");
900             break;
901           case 0x26: // '&'
902             writer.write("&amp;");
903             break;
904           case 0x22: // '"'
905             if (inAttr)
906               writer.write("&quot;");
907             else
908               writer.write(c);
909             break;
910           case 0x27: // '\''
911             if (inAttr)
912               writer.write("&apos;");
913             else
914               writer.write(c);
915             break;
916           default:
917             if (XMLParser.isXML11RestrictedChar(c))
918               writer.write("&#x" + Integer.toHexString(c) + ";");
919             else
920               {
921                 char[] chars = Character.toChars(c);
922                 writer.write(chars, 0, chars.length);
923               }
924           }
925       }
926   }
927 
isName(String text)928   private boolean isName(String text)
929     throws IOException
930   {
931     if (text == null)
932       return false;
933     int[] seq = UnicodeReader.toCodePointArray(text);
934     if (seq.length < 1)
935       return false;
936     if (!XMLParser.isNameStartCharacter(seq[0], xml11))
937       return false;
938     for (int i = 1; i < seq.length; i++)
939       {
940         if (!XMLParser.isNameCharacter(seq[i], xml11))
941           return false;
942       }
943     return true;
944   }
945 
isPrefix(String text)946   private boolean isPrefix(String text)
947     throws IOException
948   {
949     if (XMLConstants.DEFAULT_NS_PREFIX.equals(text)) {
950         return true;
951     }
952     return isNCName(text);
953   }
954 
isNCName(String text)955   private boolean isNCName(String text)
956     throws IOException
957   {
958     if (text == null)
959       return false;
960     int[] seq = UnicodeReader.toCodePointArray(text);
961     if (seq.length < 1)
962       return false;
963     if (!XMLParser.isNameStartCharacter(seq[0], xml11) || seq[0] == 0x3a)
964       return false;
965     for (int i = 1; i < seq.length; i++)
966       {
967         if (!XMLParser.isNameCharacter(seq[i], xml11) || seq[i] == 0x3a)
968           return false;
969       }
970     return true;
971   }
972 
isChars(String text)973   private boolean isChars(String text)
974     throws IOException
975   {
976     if (text == null)
977       return false;
978     int[] seq = UnicodeReader.toCodePointArray(text);
979     hasXML11RestrictedChars = false;
980     if (xml11)
981       {
982         for (int i = 0; i < seq.length; i++)
983           {
984             if (!XMLParser.isXML11Char(seq[i]))
985               return false;
986             if (XMLParser.isXML11RestrictedChar(seq[i]))
987               hasXML11RestrictedChars = true;
988           }
989       }
990     else
991       {
992         for (int i = 0; i < seq.length; i++)
993           {
994             if (!XMLParser.isChar(seq[i]))
995               return false;
996           }
997       }
998     return true;
999   }
1000 
isURI(String text)1001   private boolean isURI(String text)
1002   {
1003     if (text == null)
1004       return false;
1005     char[] chars = text.toCharArray();
1006     if (chars.length < 1)
1007       return false;
1008     for (int i = 0; i < chars.length; i++)
1009       {
1010         if (chars[i] < 0x20 || chars[i] >= 0x7f)
1011           return false;
1012       }
1013     return true;
1014   }
1015 
1016 }
1017