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.utils.xml; 023 024import java.io.IOException; 025import java.io.PrintWriter; 026import java.util.ArrayList; 027import java.util.HashMap; 028import java.util.Iterator; 029import java.util.LinkedList; 030import java.util.List; 031import 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 041public 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 namespacePrefixes = new HashMap(); 050 private int namespaceSeed = 0; 051 private LinkedList namespaceBindings = new LinkedList(); 052 private List namespacesDeclared = new ArrayList(); 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 nsi = namespacesDeclared.iterator(); nsi.hasNext(); ) { 076 String nsURI = (String) 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 bindings = (List) namespaceBindings.getLast(); 114 if (bindings == null) { 115 bindings = new ArrayList(); 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 = (String) 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 = (String) 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 hereBindings = (List) namespaceBindings.removeLast(); 196 if (hereBindings != null) { 197 for (Iterator 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 = (String) 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}