Source for gnu.xml.stream.XMLStreamWriterImpl

   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:   /**
 108:    * Constructor.
 109:    * @see #writer
 110:    * @see #encoding
 111:    * @see #prefixDefaulting
 112:    */
 113:   protected XMLStreamWriterImpl(Writer writer, String encoding,
 114:                                 boolean prefixDefaulting)
 115:   {
 116:     this.writer = writer;
 117:     this.encoding = encoding;
 118:     this.prefixDefaulting = prefixDefaulting;
 119:     elements = new LinkedList();
 120:     namespaces = new NamespaceSupport();
 121:   }
 122: 
 123:   /**
 124:    * Write the end of a start-element event.
 125:    * This will close the element if it was defined to be an empty element.
 126:    */
 127:   private void endStartElement()
 128:     throws IOException
 129:   {
 130:     if (!inStartElement)
 131:       return;
 132:     if (emptyElement)
 133:       {
 134:         writer.write('/');
 135:         elements.removeLast();
 136:         namespaces.popContext();
 137:         emptyElement = false;
 138:       }
 139:     writer.write('>');
 140:     inStartElement = false;
 141:   }
 142: 
 143:   public void writeStartElement(String localName)
 144:     throws XMLStreamException
 145:   {
 146:     try
 147:       {
 148:         endStartElement();
 149:         namespaces.pushContext();
 150:         
 151:         writer.write('<');
 152:         writer.write(localName);
 153:         
 154:         elements.addLast(new String[] { null, localName });
 155:         inStartElement = true;
 156:       }
 157:     catch (IOException e)
 158:       {
 159:         XMLStreamException e2 = new XMLStreamException(e);
 160:         e2.initCause(e);
 161:         throw e2;
 162:       }
 163:   }
 164: 
 165:   public void writeStartElement(String namespaceURI, String localName)
 166:     throws XMLStreamException
 167:   {
 168:     try
 169:       {
 170:         endStartElement();
 171:         namespaces.pushContext();
 172:         
 173:         String prefix = getPrefix(namespaceURI);
 174:         boolean isDeclared = (prefix != null);
 175:         if (!isDeclared)
 176:           {
 177:             if (prefixDefaulting)
 178:               prefix = createPrefix(namespaceURI);
 179:             else
 180:               throw new XMLStreamException("namespace " + namespaceURI +
 181:                                            " has not been declared");
 182:           }
 183:         writer.write('<');
 184:         if (!"".equals(prefix))
 185:           {
 186:             writer.write(prefix);
 187:             writer.write(':');
 188:           }
 189:         writer.write(localName);
 190:         inStartElement = true;
 191:         if (!isDeclared)
 192:           {
 193:             writeNamespace(prefix, namespaceURI);
 194:           }
 195:         
 196:         elements.addLast(new String[] { prefix, localName });
 197:       }
 198:     catch (IOException e)
 199:       {
 200:         XMLStreamException e2 = new XMLStreamException(e);
 201:         e2.initCause(e);
 202:         throw e2;
 203:       }
 204:   }
 205: 
 206:   /**
 207:    * Creates a new unique prefix in the document.
 208:    * Subclasses may override this method to provide a suitably unique prefix
 209:    * for the given namespace.
 210:    * @param namespaceURI the namespace URI
 211:    */
 212:   protected String createPrefix(String namespaceURI)
 213:   {
 214:     Set prefixes = new HashSet();
 215:     for (Enumeration e = namespaces.getPrefixes(); e.hasMoreElements(); )
 216:       prefixes.add(e.nextElement());
 217:     String ret;
 218:     do
 219:       {
 220:         ret = "ns" + (count++);
 221:       }
 222:     while (prefixes.contains(ret));
 223:     return ret;
 224:   }
 225: 
 226:   public void writeStartElement(String prefix, String localName,
 227:                                 String namespaceURI)
 228:     throws XMLStreamException
 229:   {
 230:     try
 231:       {
 232:         endStartElement();
 233:         namespaces.pushContext();
 234:         
 235:         String currentPrefix = getPrefix(namespaceURI);
 236:         boolean isCurrent = prefix.equals(currentPrefix);
 237:         writer.write('<');
 238:         if (!"".equals(prefix))
 239:           {
 240:             writer.write(prefix);
 241:             writer.write(':');
 242:           }
 243:         writer.write(localName);
 244:         if (prefixDefaulting && !isCurrent)
 245:           {
 246:             writeNamespace(prefix, namespaceURI);
 247:           }
 248:         
 249:         elements.addLast(new String[] { prefix, localName });
 250:         inStartElement = true;
 251:       }
 252:     catch (IOException e)
 253:       {
 254:         XMLStreamException e2 = new XMLStreamException(e);
 255:         e2.initCause(e);
 256:         throw e2;
 257:       }
 258:   }
 259: 
 260:   public void writeEmptyElement(String namespaceURI, String localName)
 261:     throws XMLStreamException
 262:   {
 263:     writeStartElement(namespaceURI, localName);
 264:     emptyElement = true;
 265:   }
 266: 
 267:   public void writeEmptyElement(String prefix, String localName,
 268:                                 String namespaceURI)
 269:     throws XMLStreamException
 270:   {
 271:     writeStartElement(prefix, localName, namespaceURI);
 272:     emptyElement = true;
 273:   }
 274: 
 275:   public void writeEmptyElement(String localName)
 276:     throws XMLStreamException
 277:   {
 278:     writeStartElement(localName);
 279:     emptyElement = true;
 280:   }
 281: 
 282:   public void writeEndElement()
 283:     throws XMLStreamException
 284:   {
 285:     try
 286:       {
 287:         endStartElement();
 288:         String[] element = (String[]) elements.removeLast();
 289:         namespaces.popContext();
 290: 
 291:         writer.write('<');
 292:         writer.write('/');
 293:         if (element[0] != null && !"".equals(element[0]))
 294:           {
 295:             writer.write(element[0]);
 296:             writer.write(':');
 297:           }
 298:         writer.write(element[1]);
 299:         writer.write('>');
 300:       }
 301:     catch (IOException e)
 302:       {
 303:         XMLStreamException e2 = new XMLStreamException(e);
 304:         e2.initCause(e);
 305:         throw e2;
 306:       }
 307:   }
 308: 
 309:   public void writeEndDocument()
 310:     throws XMLStreamException
 311:   {
 312:     while (!elements.isEmpty())
 313:       writeEndElement();
 314:   }
 315: 
 316:   public void close()
 317:     throws XMLStreamException
 318:   {
 319:     flush();
 320:   }
 321: 
 322:   public void flush()
 323:     throws XMLStreamException
 324:   {
 325:     try
 326:       {
 327:         writer.flush();
 328:       }
 329:     catch (IOException e)
 330:       {
 331:         XMLStreamException e2 = new XMLStreamException(e);
 332:         e2.initCause(e);
 333:         throw e2;
 334:       }
 335:   }
 336: 
 337:   public void writeAttribute(String localName, String value)
 338:     throws XMLStreamException
 339:   {
 340:     if (!inStartElement)
 341:       throw new IllegalStateException();
 342:     try
 343:       {
 344:         writer.write(' ');
 345:         writer.write(localName);
 346:         writer.write('=');
 347:         writer.write('"');
 348:         writeEncoded(value, true);
 349:         writer.write('"');
 350:       }
 351:     catch (IOException e)
 352:       {
 353:         XMLStreamException e2 = new XMLStreamException(e);
 354:         e2.initCause(e);
 355:         throw e2;
 356:       }
 357:   }
 358: 
 359:   public void writeAttribute(String prefix, String namespaceURI,
 360:                              String localName, String value)
 361:     throws XMLStreamException
 362:   {
 363:     if (!inStartElement)
 364:       throw new IllegalStateException();
 365:     try
 366:       {
 367:         String currentPrefix = getPrefix(namespaceURI);
 368:         if (currentPrefix == null)
 369:           {
 370:             if (prefixDefaulting)
 371:               writeNamespace(prefix, namespaceURI);
 372:             else
 373:               throw new XMLStreamException("namespace " + namespaceURI +
 374:                                            " is not bound");
 375:           }
 376:         else if (!currentPrefix.equals(prefix))
 377:           throw new XMLStreamException("namespace " + namespaceURI +
 378:                                        " is bound to prefix " +
 379:                                        currentPrefix);
 380:         writer.write(' ');
 381:         if (!"".equals(prefix))
 382:           {
 383:             writer.write(prefix);
 384:             writer.write(':');
 385:           }
 386:         writer.write(localName);
 387:         writer.write('=');
 388:         writer.write('"');
 389:         writeEncoded(value, true);
 390:         writer.write('"');
 391:       }
 392:     catch (IOException e)
 393:       {
 394:         XMLStreamException e2 = new XMLStreamException(e);
 395:         e2.initCause(e);
 396:         throw e2;
 397:       }
 398:   }
 399: 
 400:   public void writeAttribute(String namespaceURI, String localName,
 401:                              String value)
 402:     throws XMLStreamException
 403:   {
 404:     if (!inStartElement)
 405:       throw new IllegalStateException();
 406:     try
 407:       {
 408:         String prefix = getPrefix(namespaceURI);
 409:         if (prefix == null)
 410:           {
 411:             if (prefixDefaulting)
 412:               {
 413:                 prefix = XMLConstants.DEFAULT_NS_PREFIX;
 414:                 writeNamespace(prefix, namespaceURI);
 415:               }
 416:             else
 417:               throw new XMLStreamException("namespace " + namespaceURI +
 418:                                            " is not bound");
 419:           }
 420:         writer.write(' ');
 421:         if (!"".equals(prefix))
 422:           {
 423:             writer.write(prefix);
 424:             writer.write(':');
 425:           }
 426:         writer.write(localName);
 427:         writer.write('=');
 428:         writer.write('"');
 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: 
 440:   public void writeNamespace(String prefix, String namespaceURI)
 441:     throws XMLStreamException
 442:   {
 443:     if (!inStartElement)
 444:       throw new IllegalStateException();
 445:     try
 446:       {
 447:         if (prefix == null)
 448:           prefix = XMLConstants.DEFAULT_NS_PREFIX;
 449: 
 450:         setPrefix(prefix, namespaceURI);
 451:         
 452:         writer.write(' ');
 453:         writer.write("xmlns");
 454:         if (!XMLConstants.DEFAULT_NS_PREFIX.equals(prefix))
 455:           {
 456:             writer.write(':');
 457:             writer.write(prefix);
 458:           }
 459:         writer.write('=');
 460:         writer.write('"');
 461:         writer.write(namespaceURI);
 462:         writer.write('"');
 463:       }
 464:     catch (IOException e)
 465:       {
 466:         XMLStreamException e2 = new XMLStreamException(e);
 467:         e2.initCause(e);
 468:         throw e2;
 469:       }
 470:   }
 471: 
 472:   public void writeDefaultNamespace(String namespaceURI)
 473:     throws XMLStreamException
 474:   {
 475:     writeNamespace(XMLConstants.DEFAULT_NS_PREFIX, namespaceURI);
 476:   }
 477: 
 478:   public void writeComment(String data)
 479:     throws XMLStreamException
 480:   {
 481:     try
 482:       {
 483:         endStartElement();
 484:         
 485:         if (data != null && data.indexOf("--") != -1)
 486:           throw new IllegalArgumentException(data);
 487:         
 488:         writer.write("<!--");
 489:         if (data != null)
 490:           writer.write(data);
 491:         writer.write("-->");
 492:       }
 493:     catch (IOException e)
 494:       {
 495:         XMLStreamException e2 = new XMLStreamException(e);
 496:         e2.initCause(e);
 497:         throw e2;
 498:       }
 499:   }
 500: 
 501:   public void writeProcessingInstruction(String target)
 502:     throws XMLStreamException
 503:   {
 504:     writeProcessingInstruction(target, null);
 505:   }
 506: 
 507:   public void writeProcessingInstruction(String target, String data)
 508:     throws XMLStreamException
 509:   {
 510:     try
 511:       {
 512:         endStartElement();
 513: 
 514:         writer.write('<');
 515:         writer.write('?');
 516:         writer.write(target);
 517:         if (data != null)
 518:           {
 519:             writer.write(' ');
 520:             writer.write(data);
 521:           }
 522:         writer.write('?');
 523:         writer.write('>');
 524:       }
 525:     catch (IOException e)
 526:       {
 527:         XMLStreamException e2 = new XMLStreamException(e);
 528:         e2.initCause(e);
 529:         throw e2;
 530:       }
 531:   }
 532: 
 533:   public void writeCData(String data)
 534:     throws XMLStreamException
 535:   {
 536:     try
 537:       {
 538:         endStartElement();
 539: 
 540:         if (data.indexOf("]]") != -1)
 541:           throw new IllegalArgumentException(data);
 542: 
 543:         writer.write("<![CDATA[");
 544:         writer.write(data);
 545:         writer.write("]]>");
 546:       }
 547:     catch (IOException e)
 548:       {
 549:         XMLStreamException e2 = new XMLStreamException(e);
 550:         e2.initCause(e);
 551:         throw e2;
 552:       }
 553:   }
 554: 
 555:   public void writeDTD(String dtd)
 556:     throws XMLStreamException
 557:   {
 558:     try
 559:       {
 560:         writer.write("<!DOCTYPE ");
 561:         writer.write(dtd);
 562:         writer.write('>');
 563:       }
 564:     catch (IOException e)
 565:       {
 566:         XMLStreamException e2 = new XMLStreamException(e);
 567:         e2.initCause(e);
 568:         throw e2;
 569:       }
 570:   }
 571: 
 572:   public void writeEntityRef(String name)
 573:     throws XMLStreamException
 574:   {
 575:     try
 576:       {
 577:         endStartElement();
 578: 
 579:         writer.write('&');
 580:         writer.write(name);
 581:         writer.write(';');
 582:       }
 583:     catch (IOException e)
 584:       {
 585:         XMLStreamException e2 = new XMLStreamException(e);
 586:         e2.initCause(e);
 587:         throw e2;
 588:       }
 589:   }
 590: 
 591:   public void writeStartDocument()
 592:     throws XMLStreamException
 593:   {
 594:     writeStartDocument(null, null);
 595:   }
 596: 
 597:   public void writeStartDocument(String version)
 598:     throws XMLStreamException
 599:   {
 600:     writeStartDocument(null, version);
 601:   }
 602: 
 603:   public void writeStartDocument(String encoding, String version)
 604:     throws XMLStreamException
 605:   {
 606:     if (version == null)
 607:       version = "1.0";
 608:     encoding = this.encoding; // YES: the parameter must be ignored
 609:     if (encoding == null)
 610:       encoding = "UTF-8";
 611:     if (!"1.0".equals(version) && !"1.1".equals(version))
 612:       throw new IllegalArgumentException(version);
 613:     try
 614:       {
 615:         writer.write("<?xml version=\"");
 616:         writer.write(version);
 617:         writer.write("\" encoding=\"");
 618:         writer.write(encoding);
 619:         writer.write("\"?>");
 620:         writer.write(System.getProperty("line.separator"));
 621:       }
 622:     catch (IOException e)
 623:       {
 624:         XMLStreamException e2 = new XMLStreamException(e);
 625:         e2.initCause(e);
 626:         throw e2;
 627:       }
 628:   }
 629: 
 630:   public void writeCharacters(String text)
 631:     throws XMLStreamException
 632:   {
 633:     try
 634:       {
 635:         endStartElement();
 636: 
 637:         if (text != null)
 638:           writeEncoded(text, false);
 639:       }
 640:     catch (IOException e)
 641:       {
 642:         XMLStreamException e2 = new XMLStreamException(e);
 643:         e2.initCause(e);
 644:         throw e2;
 645:       }
 646:   }
 647: 
 648:   public void writeCharacters(char[] text, int start, int len)
 649:     throws XMLStreamException
 650:   {
 651:     try
 652:       {
 653:         endStartElement();
 654: 
 655:         int end = start + len;
 656:         len = 0;
 657:         for (int i = start; i < end; i++)
 658:           {
 659:             char c = text[i];
 660:             if (c == '<' || c == '>' || c == '&')
 661:               {
 662:                 writer.write(text, start, len);
 663:                 if (c == '<')
 664:                   writer.write("&lt;");
 665:                 else if (c == '>')
 666:                   writer.write("&gt;");
 667:                 else
 668:                   writer.write("&amp;");
 669:                 start = i + 1;
 670:                 len = 0;
 671:               }
 672:             else
 673:               len++;
 674:           }
 675:         if (len > 0)
 676:           writer.write(text, start, len);
 677:       }
 678:     catch (IOException e)
 679:       {
 680:         XMLStreamException e2 = new XMLStreamException(e);
 681:         e2.initCause(e);
 682:         throw e2;
 683:       }
 684:   }
 685: 
 686:   public String getPrefix(String uri)
 687:     throws XMLStreamException
 688:   {
 689:     String prefix = namespaces.getPrefix(uri);
 690:     if (prefix == null && namespaceContext != null)
 691:       prefix = namespaceContext.getPrefix(uri);
 692:     return prefix;
 693:   }
 694: 
 695:   public void setPrefix(String prefix, String uri)
 696:     throws XMLStreamException
 697:   {
 698:     if (!namespaces.declarePrefix(prefix, uri))
 699:       throw new XMLStreamException("illegal prefix " + prefix);
 700:   }
 701: 
 702:   public void setDefaultNamespace(String uri)
 703:     throws XMLStreamException
 704:   {
 705:     if (!namespaces.declarePrefix(XMLConstants.DEFAULT_NS_PREFIX, uri))
 706:       throw new XMLStreamException("illegal default namespace prefix");
 707:   }
 708: 
 709:   public void setNamespaceContext(NamespaceContext context)
 710:     throws XMLStreamException
 711:   {
 712:     namespaceContext = context;
 713:   }
 714: 
 715:   public NamespaceContext getNamespaceContext()
 716:   {
 717:     return namespaceContext;
 718:   }
 719: 
 720:   public Object getProperty(String name)
 721:     throws IllegalArgumentException
 722:   {
 723:     throw new IllegalArgumentException(name);
 724:   }
 725: 
 726:   /**
 727:    * Write the specified text, ensuring that the content is suitably encoded
 728:    * for XML.
 729:    * @param text the text to write
 730:    * @param inAttr whether we are in an attribute value
 731:    */
 732:   private void writeEncoded(String text, boolean inAttr)
 733:     throws IOException
 734:   {
 735:     char[] chars = text.toCharArray();
 736:     int start = 0;
 737:     int end = chars.length;
 738:     int len = 0;
 739:     for (int i = start; i < end; i++)
 740:       {
 741:         char c = chars[i];
 742:         if (c == '<' || c == '>' || c == '&')
 743:           {
 744:             writer.write(chars, start, len);
 745:             if (c == '<')
 746:               writer.write("&lt;");
 747:             else if (c == '>')
 748:               writer.write("&gt;");
 749:             else
 750:               writer.write("&amp;");
 751:             start = i + 1;
 752:             len = 0;
 753:           }
 754:         else if (inAttr && (c == '"' || c == '\''))
 755:           {
 756:             writer.write(chars, start, len);
 757:             if (c == '"')
 758:               writer.write("&quot;");
 759:             else
 760:               writer.write("&apos;");
 761:             start = i + 1;
 762:             len = 0;
 763:           }
 764:         else
 765:           len++;
 766:       }
 767:     if (len > 0)
 768:       writer.write(chars, start, len);
 769:   }
 770:   
 771: }