001
002 /*
003 * Copyright (C) 2012 Archie L. Cobbs. All rights reserved.
004 *
005 * $Id: AnnotatedXMLEventWriter.java 272 2012-02-02 21:02:52Z archie.cobbs $
006 */
007
008 package org.dellroad.stuff.xml;
009
010 import javax.xml.namespace.NamespaceContext;
011 import javax.xml.stream.XMLEventFactory;
012 import javax.xml.stream.XMLEventReader;
013 import javax.xml.stream.XMLEventWriter;
014 import javax.xml.stream.XMLStreamException;
015 import javax.xml.stream.events.XMLEvent;
016
017 /**
018 * {@link XMLEventWriter} that adds an extra annotation element to an XML document as it is written.
019 * The annotation element will be added as the first element inside the top-level document element.
020 *
021 * <p>
022 * This class can be used in combination with {@link AnnotatedXMLEventReader} to transparently annotate XML documents.
023 *
024 * @see AnnotatedXMLEventReader
025 */
026 public abstract class AnnotatedXMLEventWriter implements XMLEventWriter {
027
028 protected final XMLEventFactory xmlEventFactory = XMLEventFactory.newFactory();
029
030 private final StringBuilder trailingSpace = new StringBuilder();
031 private final XMLEventWriter inner;
032
033 // State:
034 // 0 = before document element
035 // 1 = after document element but before ananotation element
036 // 2 = after annotation element
037 private byte state;
038
039 public AnnotatedXMLEventWriter(XMLEventWriter inner) {
040 if (inner == null)
041 throw new IllegalArgumentException("null inner");
042 this.inner = inner;
043 }
044
045 @Override
046 public void add(XMLEvent event) throws XMLStreamException {
047 switch (this.state) {
048 case 0:
049 if (event.isStartElement())
050 this.state++;
051 this.inner.add(event);
052 break;
053 case 1:
054 if (event.isCharacters() && event.asCharacters().isWhiteSpace()) {
055 this.trailingSpace.append(event.asCharacters().getData());
056 this.inner.add(event);
057 break;
058 }
059 this.state++;
060 this.addAnnotationElement();
061 if (this.trailingSpace.length() > 0)
062 this.inner.add(this.xmlEventFactory.createCharacters(this.trailingSpace.toString()));
063 this.inner.add(event);
064 break;
065 case 2:
066 this.inner.add(event);
067 break;
068 default:
069 throw new RuntimeException("internal error");
070 }
071 }
072
073 @Override
074 public void add(XMLEventReader reader) throws XMLStreamException {
075 if (reader == null)
076 throw new XMLStreamException("null reader");
077 while (reader.hasNext())
078 this.add(reader.nextEvent());
079 }
080
081 @Override
082 public void flush() throws XMLStreamException {
083 this.inner.flush();
084 }
085
086 @Override
087 public void close() throws XMLStreamException {
088 this.inner.close();
089 }
090
091 @Override
092 public String getPrefix(String uri) throws XMLStreamException {
093 return this.inner.getPrefix(uri);
094 }
095
096 @Override
097 public void setPrefix(String prefix, String uri) throws XMLStreamException {
098 this.inner.setPrefix(prefix, uri);
099 }
100
101 @Override
102 public void setDefaultNamespace(String uri) throws XMLStreamException {
103 this.inner.setDefaultNamespace(uri);
104 }
105
106 @Override
107 public void setNamespaceContext(NamespaceContext context) throws XMLStreamException {
108 this.inner.setNamespaceContext(context);
109 }
110
111 @Override
112 public NamespaceContext getNamespaceContext() {
113 return this.inner.getNamespaceContext();
114 }
115
116 /**
117 * Get the whitespace found between the opening document tag and the first non-space child.
118 */
119 protected String getTrailingSpace() {
120 return this.trailingSpace.toString();
121 }
122
123 /**
124 * Add the annotation element.
125 *
126 * <p>
127 * This method should {@link #add add()} the {@link javax.xml.stream.events.StartElement} for the annotation element, followed
128 * by any nested content, and then lastly the {@link javax.xml.stream.events.EndElement} for the annotation element.
129 */
130 protected abstract void addAnnotationElement() throws XMLStreamException;
131 }
132