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 022package org.biojava.nbio.core.util; 023 024import java.io.IOException; 025import java.io.PrintWriter; 026import java.util.*; 027 028/** 029 * Implementation of XMLWriter which emits nicely formatted documents 030 * to a PrintWriter. 031 * 032 * @author Thomas Down 033 * @since 1.3 034 */ 035 036public class PrettyXMLWriter implements XMLWriter { 037 private int indentUnit = 2; 038 039 private PrintWriter writer; 040 private boolean isOpeningTag = false; 041 private boolean afterNewline = true; 042 private int indent = 0; 043 044 private Map<String, String> namespacePrefixes = new HashMap<String, String>(); 045 private int namespaceSeed = 0; 046 private LinkedList<List<String>> namespaceBindings = new LinkedList<List<String>>(); 047 private List<String> namespacesDeclared = new ArrayList<String>(); 048 049 public PrettyXMLWriter(PrintWriter writer) { 050 this.writer = writer; 051 } 052 053 @Override 054 public void declareNamespace(String nsURI, String prefixHint) 055 throws IOException 056 { 057 if (!namespacePrefixes.containsKey(nsURI)) { 058 if (isOpeningTag) { 059 String prefix = allocPrefix(nsURI); 060 attribute("xmlns:" + prefix, nsURI); 061 } else { 062 namespacesDeclared.add(nsURI); 063 } 064 } 065 } 066 067 private void handleDeclaredNamespaces() 068 throws IOException 069 { 070 if (namespacesDeclared.size() == 0) { 071 for (Iterator<String> nsi = namespacesDeclared.iterator(); nsi.hasNext(); ) { 072 String nsURI = nsi.next(); 073 if (!namespacePrefixes.containsKey(nsURI)) { 074 String prefix = allocPrefix(nsURI); 075 attribute("xmlns:" + prefix, nsURI); 076 } 077 } 078 namespacesDeclared.clear(); 079 } 080 } 081 082 protected void writeIndent() 083 throws IOException 084 { 085 for (int i = 0; i < indent * indentUnit; ++i) { 086 writer.write(' '); 087 } 088 } 089 090 private void _openTag() 091 throws IOException 092 { 093 if (isOpeningTag) { 094 writer.println('>'); 095 afterNewline = true; 096 } 097 if (afterNewline) { 098 writeIndent(); 099 } 100 indent++; 101 isOpeningTag = true; 102 afterNewline = false; 103 namespaceBindings.add(null); 104 } 105 106 private String allocPrefix(String nsURI) { 107 String prefix = "ns" + (++namespaceSeed); 108 namespacePrefixes.put(nsURI, prefix); 109 List<String> bindings = namespaceBindings.getLast(); 110 if (bindings == null) { 111 bindings = new ArrayList<String>(); 112 namespaceBindings.removeLast(); 113 namespaceBindings.add(bindings); 114 } 115 bindings.add(nsURI); 116 return prefix; 117 } 118 119 @Override 120 public void openTag(String nsURI, String localName) 121 throws IOException 122 { 123 if (nsURI == null || nsURI.length() == 0) 124 { 125 throw new IOException("Invalid namespace URI: "+nsURI); 126 } 127 _openTag(); 128 boolean alloced = false; 129 String prefix = namespacePrefixes.get(nsURI); 130 if (prefix == null) { 131 prefix = allocPrefix(nsURI); 132 alloced = true; 133 } 134 writer.print('<'); 135 writer.print(prefix); 136 writer.print(':'); 137 writer.print(localName); 138 if (alloced) { 139 attribute("xmlns:" + prefix, nsURI); 140 } 141 handleDeclaredNamespaces(); 142 } 143 144 @Override 145 public void openTag(String qName) 146 throws IOException 147 { 148 _openTag(); 149 writer.print('<'); 150 writer.print(qName); 151 handleDeclaredNamespaces(); 152 } 153 154 @Override 155 public void attribute(String nsURI, String localName, String value) 156 throws IOException 157 { 158 if (! isOpeningTag) { 159 throw new IOException("attributes must follow an openTag"); 160 } 161 162 String prefix = namespacePrefixes.get(nsURI); 163 if (prefix == null) { 164 prefix = allocPrefix(nsURI); 165 attribute("xmlns:" + prefix, nsURI); 166 } 167 168 writer.print(' '); 169 writer.print(prefix); 170 writer.print(':'); 171 writer.print(localName); 172 writer.print("=\""); 173 printAttributeValue(value); 174 writer.print('"'); 175 } 176 177 @Override 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 @Override 204 public void closeTag(String nsURI, String localName) 205 throws IOException 206 { 207 String prefix = namespacePrefixes.get(nsURI); 208 if (prefix == null) { 209 throw new IOException("Assertion failed: unknown namespace when closing tag"); 210 } 211 indent--; 212 213 if (isOpeningTag) { 214 writer.println(" />"); 215 } else { 216 if (afterNewline) { 217 writeIndent(); 218 } 219 writer.print("</"); 220 writer.print(prefix); 221 writer.print(':'); 222 writer.print(localName); 223 writer.println('>'); 224 } 225 _closeTag(); 226 } 227 228 @Override 229 public void closeTag(String qName) 230 throws IOException 231 { 232 indent--; 233 234 if (isOpeningTag) { 235 writer.println(" />"); 236 } else { 237 if (afterNewline) { 238 writeIndent(); 239 } 240 writer.print("</"); 241 writer.print(qName); 242 writer.println('>'); 243 } 244 _closeTag(); 245 } 246 247 @Override 248 public void println(String data) 249 throws IOException 250 { 251 if (isOpeningTag) { 252 writer.println('>'); 253 isOpeningTag = false; 254 } 255 printChars(data); 256 writer.println(); 257 afterNewline = true; 258 } 259 260 @Override 261 public void print(String data) 262 throws IOException 263 { 264 if (isOpeningTag) { 265 writer.print('>'); 266 isOpeningTag = false; 267 } 268 printChars(data); 269 afterNewline = false; 270 } 271 272 @Override 273 public void printRaw(String data) 274 throws IOException 275 { 276 writer.println(data); 277 } 278 279 protected void printChars(String data) 280 throws IOException 281 { 282 if (data == null) { 283 printChars("null"); 284 return; 285 } 286 287 for (int pos = 0; pos < data.length(); ++pos) { 288 char c = data.charAt(pos); 289 if (c == '<' || c == '>' || c == '&') { 290 numericalEntity(c); 291 } else { 292 writer.write(c); 293 } 294 } 295 } 296 297 protected void printAttributeValue(String data) 298 throws IOException 299 { 300 if (data == null) { 301 printAttributeValue("null"); 302 return; 303 } 304 305 for (int pos = 0; pos < data.length(); ++pos) { 306 char c = data.charAt(pos); 307 if (c == '<' || c == '>' || c == '&' || c == '"') { 308 numericalEntity(c); 309 } else { 310 writer.write(c); 311 } 312 } 313 } 314 315 protected void numericalEntity(char c) 316 throws IOException 317 { 318 writer.print("&#"); 319 writer.print((int) c); 320 writer.print(';'); 321 } 322 323 @Override 324 public void close() 325 throws IOException 326 { 327 writer.close(); 328 } 329}