001 /*
002 * BioJava development code
003 *
004 * This code may be freely distributed and modified under the
005 * terms of the GNU Lesser General Public Licence. This should
006 * be distributed with the code. If you do not have a copy,
007 * see:
008 *
009 * http://www.gnu.org/copyleft/lesser.html
010 *
011 * Copyright for this code is held jointly by the individual
012 * authors. These should be listed in @author doc comments.
013 *
014 * For more information on the BioJava project and its aims,
015 * or to join the biojava-l mailing list, visit the home page
016 * at:
017 *
018 * http://www.biojava.org/
019 *
020 */
021
022 package org.biojava3.core.util;
023
024 import java.io.IOException;
025 import java.io.PrintWriter;
026 import java.util.ArrayList;
027 import java.util.HashMap;
028 import java.util.Iterator;
029 import java.util.LinkedList;
030 import java.util.List;
031 import java.util.Map;
032
033 /**
034 * Implementation of XMLWriter which emits nicely formatted documents
035 * to a PrintWriter.
036 *
037 * @author Thomas Down
038 * @since 1.3
039 */
040
041 public class PrettyXMLWriter implements XMLWriter {
042 private int indentUnit = 2;
043
044 private PrintWriter writer;
045 private boolean isOpeningTag = false;
046 private boolean afterNewline = true;
047 private int indent = 0;
048
049 private Map<String, String> namespacePrefixes = new HashMap<String, String>();
050 private int namespaceSeed = 0;
051 private LinkedList<List<String>> namespaceBindings = new LinkedList<List<String>>();
052 private List<String> namespacesDeclared = new ArrayList<String>();
053
054 public PrettyXMLWriter(PrintWriter writer) {
055 this.writer = writer;
056 }
057
058 public void declareNamespace(String nsURI, String prefixHint)
059 throws IOException
060 {
061 if (!namespacePrefixes.containsKey(nsURI)) {
062 if (isOpeningTag == true) {
063 String prefix = allocPrefix(nsURI);
064 attribute("xmlns:" + prefix, nsURI);
065 } else {
066 namespacesDeclared.add(nsURI);
067 }
068 }
069 }
070
071 private void handleDeclaredNamespaces()
072 throws IOException
073 {
074 if (namespacesDeclared.size() == 0) {
075 for (Iterator<String> nsi = namespacesDeclared.iterator(); nsi.hasNext(); ) {
076 String nsURI = nsi.next();
077 if (!namespacePrefixes.containsKey(nsURI)) {
078 String prefix = allocPrefix(nsURI);
079 attribute("xmlns:" + prefix, nsURI);
080 }
081 }
082 namespacesDeclared.clear();
083 }
084 }
085
086 protected void writeIndent()
087 throws IOException
088 {
089 for (int i = 0; i < indent * indentUnit; ++i) {
090 writer.write(' ');
091 }
092 }
093
094 private void _openTag()
095 throws IOException
096 {
097 if (isOpeningTag) {
098 writer.println('>');
099 afterNewline = true;
100 }
101 if (afterNewline) {
102 writeIndent();
103 }
104 indent++;
105 isOpeningTag = true;
106 afterNewline = false;
107 namespaceBindings.add(null);
108 }
109
110 private String allocPrefix(String nsURI) {
111 String prefix = "ns" + (++namespaceSeed);
112 namespacePrefixes.put(nsURI, prefix);
113 List<String> bindings = namespaceBindings.getLast();
114 if (bindings == null) {
115 bindings = new ArrayList<String>();
116 namespaceBindings.removeLast();
117 namespaceBindings.add(bindings);
118 }
119 bindings.add(nsURI);
120 return prefix;
121 }
122
123 public void openTag(String nsURI, String localName)
124 throws IOException
125 {
126 if (nsURI == null || nsURI.length() == 0)
127 {
128 throw new IOException("Invalid namespace URI: "+nsURI);
129 }
130 _openTag();
131 boolean alloced = false;
132 String prefix = namespacePrefixes.get(nsURI);
133 if (prefix == null) {
134 prefix = allocPrefix(nsURI);
135 alloced = true;
136 }
137 writer.print('<');
138 writer.print(prefix);
139 writer.print(':');
140 writer.print(localName);
141 if (alloced) {
142 attribute("xmlns:" + prefix, nsURI);
143 }
144 handleDeclaredNamespaces();
145 }
146
147 public void openTag(String qName)
148 throws IOException
149 {
150 _openTag();
151 writer.print('<');
152 writer.print(qName);
153 handleDeclaredNamespaces();
154 }
155
156 public void attribute(String nsURI, String localName, String value)
157 throws IOException
158 {
159 if (! isOpeningTag) {
160 throw new IOException("attributes must follow an openTag");
161 }
162
163 String prefix = namespacePrefixes.get(nsURI);
164 if (prefix == null) {
165 prefix = allocPrefix(nsURI);
166 attribute("xmlns:" + prefix, nsURI);
167 }
168
169 writer.print(' ');
170 writer.print(prefix);
171 writer.print(':');
172 writer.print(localName);
173 writer.print("=\"");
174 printAttributeValue(value);
175 writer.print('"');
176 }
177
178 public void attribute(String qName, String value)
179 throws IOException
180 {
181 if (! isOpeningTag) {
182 throw new IOException("attributes must follow an openTag");
183 }
184
185 writer.print(' ');
186 writer.print(qName);
187 writer.print("=\"");
188 printAttributeValue(value);
189 writer.print('"');
190 }
191
192 private void _closeTag() {
193 isOpeningTag = false;
194 afterNewline = true;
195 List<String> hereBindings = namespaceBindings.removeLast();
196 if (hereBindings != null) {
197 for (Iterator<String> bi = hereBindings.iterator(); bi.hasNext(); ) {
198 namespacePrefixes.remove(bi.next());
199 }
200 }
201 }
202
203 public void closeTag(String nsURI, String localName)
204 throws IOException
205 {
206 String prefix = namespacePrefixes.get(nsURI);
207 if (prefix == null) {
208 throw new IOException("Assertion failed: unknown namespace when closing tag");
209 }
210 indent--;
211
212 if (isOpeningTag) {
213 writer.println(" />");
214 } else {
215 if (afterNewline) {
216 writeIndent();
217 }
218 writer.print("</");
219 writer.print(prefix);
220 writer.print(':');
221 writer.print(localName);
222 writer.println('>');
223 }
224 _closeTag();
225 }
226
227 public void closeTag(String qName)
228 throws IOException
229 {
230 indent--;
231
232 if (isOpeningTag) {
233 writer.println(" />");
234 } else {
235 if (afterNewline) {
236 writeIndent();
237 }
238 writer.print("</");
239 writer.print(qName);
240 writer.println('>');
241 }
242 _closeTag();
243 }
244
245 public void println(String data)
246 throws IOException
247 {
248 if (isOpeningTag) {
249 writer.println('>');
250 isOpeningTag = false;
251 }
252 printChars(data);
253 writer.println();
254 afterNewline = true;
255 }
256
257 public void print(String data)
258 throws IOException
259 {
260 if (isOpeningTag) {
261 writer.print('>');
262 isOpeningTag = false;
263 }
264 printChars(data);
265 afterNewline = false;
266 }
267
268 public void printRaw(String data)
269 throws IOException
270 {
271 writer.println(data);
272 }
273
274 protected void printChars(String data)
275 throws IOException
276 {
277 if (data == null) {
278 printChars("null");
279 return;
280 }
281
282 for (int pos = 0; pos < data.length(); ++pos) {
283 char c = data.charAt(pos);
284 if (c == '<' || c == '>' || c == '&') {
285 numericalEntity(c);
286 } else {
287 writer.write(c);
288 }
289 }
290 }
291
292 protected void printAttributeValue(String data)
293 throws IOException
294 {
295 if (data == null) {
296 printAttributeValue("null");
297 return;
298 }
299
300 for (int pos = 0; pos < data.length(); ++pos) {
301 char c = data.charAt(pos);
302 if (c == '<' || c == '>' || c == '&' || c == '"') {
303 numericalEntity(c);
304 } else {
305 writer.write(c);
306 }
307 }
308 }
309
310 protected void numericalEntity(char c)
311 throws IOException
312 {
313 writer.print("&#");
314 writer.print((int) c);
315 writer.print(';');
316 }
317
318 public void close()
319 throws IOException
320 {
321 writer.close();
322 }
323 }