001/* 002 * Copyright (c) 2004-2012, Willem Cazander 003 * All rights reserved. 004 * 005 * Redistribution and use in source and binary forms, with or without modification, are permitted provided 006 * that the following conditions are met: 007 * 008 * * Redistributions of source code must retain the above copyright notice, this list of conditions and the 009 * following disclaimer. 010 * * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and 011 * the following disclaimer in the documentation and/or other materials provided with the distribution. 012 * 013 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 014 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 015 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 016 * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 017 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT 018 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 019 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 020 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 021 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 022 */ 023 024package org.x4o.xml.sax; 025 026import java.io.IOException; 027import java.io.OutputStream; 028import java.io.OutputStreamWriter; 029import java.io.UnsupportedEncodingException; 030import java.io.Writer; 031import java.util.ArrayList; 032import java.util.HashMap; 033import java.util.List; 034import java.util.Map; 035import java.util.Set; 036 037import org.xml.sax.Attributes; 038import org.xml.sax.Locator; 039import org.xml.sax.SAXException; 040import org.xml.sax.ext.DefaultHandler2; 041 042 043/** 044 * Writes SAX event to XML. 045 * 046 * @author Willem Cazander 047 * @version 1.0 17/04/2005 048 */ 049public class XMLWriter extends DefaultHandler2 { 050 051 private Writer out = null; 052 private int indent = 0; 053 private Map<String,String> prefixMapping = new HashMap<String,String>(10); 054 private List<String> printedMappings = new ArrayList<String>(10); 055 private StringBuffer startElement = null; 056 private boolean printedReturn = false; 057 058 public XMLWriter(Writer out) { 059 this.out = out; 060 } 061 062 public XMLWriter(OutputStream out) throws UnsupportedEncodingException { 063 this.out = new OutputStreamWriter(out, "UTF-8"); 064 } 065 066 /** 067 * @see org.xml.sax.ContentHandler#startDocument() 068 */ 069 @Override 070 public void startDocument() throws SAXException { 071 indent = 0; 072 try { 073 out.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); 074 } catch (IOException e) { 075 throw new SAXException(e); 076 } 077 } 078 079 /** 080 * @see org.xml.sax.ContentHandler#endDocument() 081 */ 082 @Override 083 public void endDocument() throws SAXException { 084 try { 085 out.flush(); 086 } catch (IOException e) { 087 throw new SAXException(e); 088 } 089 } 090 091 /** 092 * @see org.xml.sax.ContentHandler#startElement(java.lang.String, java.lang.String, java.lang.String, org.xml.sax.Attributes) 093 */ 094 @Override 095 public void startElement(String uri, String localName, String name,Attributes atts) throws SAXException { 096 try { 097 if (startElement!=null) { 098 out.write(startElement.toString()); 099 startElement=null; 100 } 101 102 startElement = new StringBuffer(200); 103 104 if (printedReturn==false) { 105 startElement.append("\r\n"); 106 } 107 printedReturn=false; 108 109 for (int i = 0; i < indent; i++) { 110 startElement.append('\t'); 111 } 112 startElement.append("<"); 113 114 if (localName==null) { 115 localName = "null"; 116 } 117 118 if ("".equals(uri) | uri==null) { 119 startElement.append(localName); 120 } else { 121 String prefix = prefixMapping.get(uri); 122 if (prefix==null) { 123 throw new SAXException("preFixUri: "+uri+" is not started."); 124 } 125 if ("".equals(prefix)==false) { 126 startElement.append(prefix); 127 startElement.append(':'); 128 } 129 startElement.append(localName); 130 } 131 132 133 if ((uri!=null & "".equals(uri)==false) && printedMappings.contains(uri)==false) { 134 String prefix = prefixMapping.get(uri); 135 if (prefix==null) { 136 throw new SAXException("preFixUri: "+uri+" is not started."); 137 } 138 printedMappings.add(uri); 139 140 startElement.append(' '); 141 startElement.append("xmlns"); 142 if ("".equals(prefix)==false) { 143 startElement.append(':'); 144 startElement.append(prefix); 145 } 146 startElement.append("=\""); 147 startElement.append(uri); 148 startElement.append('"'); 149 150 boolean first = true; 151 for (String uri2:prefixMapping.keySet()) { 152 if (printedMappings.contains(uri2)==false) { 153 prefix = prefixMapping.get(uri2); 154 if (prefix==null) { 155 throw new SAXException("preFixUri: "+uri+" is not started."); 156 } 157 printedMappings.add(uri2); 158 159 if (first) { 160 startElement.append('\n'); 161 first = false; 162 } 163 164 startElement.append(' '); 165 startElement.append("xmlns"); 166 if ("".equals(prefix)==false) { 167 startElement.append(':'); 168 startElement.append(prefix); 169 } 170 startElement.append("=\""); 171 startElement.append(uri2); 172 startElement.append('"'); 173 startElement.append('\n'); 174 } 175 } 176 } 177 178 for (int i=0;i<atts.getLength();i++) { 179 String attributeUri = atts.getURI(i); 180 String attributeName = atts.getLocalName(i); 181 String attributeValue = atts.getValue(i); 182 if (attributeValue==null) { 183 attributeValue = "null"; 184 } 185 startElement.append(' '); 186 if ("".equals(attributeUri) | attributeUri ==null) { 187 startElement.append(attributeName); 188 } else { 189 startElement.append(attributeUri); 190 startElement.append(':'); 191 startElement.append(attributeName); 192 } 193 194 startElement.append("=\""); 195 196 // TODO: xml escaping of attributes 197 if (attributeValue.contains("&")) { 198 attributeValue=attributeValue.replaceAll("&", "&"); 199 } 200 if (attributeValue.contains("\"")) { 201 attributeValue=attributeValue.replaceAll("\"", ""e;"); 202 } 203 if (attributeValue.contains("<")) { 204 attributeValue=attributeValue.replaceAll("<", "<"); 205 } 206 if (attributeValue.contains(">")) { 207 attributeValue=attributeValue.replaceAll(">", ">"); 208 } 209 210 startElement.append(attributeValue); 211 startElement.append('"'); 212 } 213 startElement.append(">"); 214 } catch (IOException e) { 215 throw new SAXException(e); 216 } finally { 217 indent++; 218 } 219 } 220 221 /** 222 * @see org.xml.sax.ContentHandler#endElement(java.lang.String, java.lang.String, java.lang.String) 223 */ 224 @Override 225 public void endElement(String uri, String localName, String name) throws SAXException { 226 try { 227 if (startElement!=null) { 228 String ss = startElement.toString(); 229 out.write(ss,0,ss.length()-1); 230 out.write("/>"); 231 startElement=null; 232 indent--; 233 return; 234 } 235 236 if (printedReturn==false) { 237 out.write("\r\n"); 238 } 239 printedReturn=false; 240 indent--; 241 indent(); 242 243 if (localName==null) { 244 localName = "null"; 245 } 246 247 out.write("</"); 248 if ("".equals(uri) | uri==null) { 249 out.write(localName); 250 } else { 251 String prefix = prefixMapping.get(uri); 252 if (prefix==null) { 253 throw new SAXException("preFixUri: "+uri+" is not started."); 254 } 255 if ("".equals(prefix)==false) { 256 out.write(prefix); 257 out.write(':'); 258 } 259 out.write(localName); 260 } 261 out.write(">"); 262 } catch (IOException e) { 263 throw new SAXException(e); 264 } 265 } 266 267 /** 268 * @see org.xml.sax.ContentHandler#startPrefixMapping(java.lang.String, java.lang.String) 269 */ 270 @Override 271 public void startPrefixMapping(String prefix, String uri) throws SAXException { 272 prefixMapping.put(uri, prefix); 273 } 274 275 /** 276 * @see org.xml.sax.ContentHandler#endPrefixMapping(java.lang.String) 277 */ 278 @Override 279 public void endPrefixMapping(String prefix) throws SAXException { 280 Set<Map.Entry<String,String>> s=prefixMapping.entrySet(); 281 String uri = null; 282 for (Map.Entry<String,String> e:s) { 283 if (e.getValue()==null) { 284 continue; // way ? 285 } 286 if (e.getValue().equals(prefix)) { 287 uri = e.getKey(); 288 } 289 } 290 if (uri!=null) { 291 printedMappings.remove(uri); 292 prefixMapping.remove(uri); 293 } 294 } 295 296 /** 297 * @see org.xml.sax.ContentHandler#characters(char[], int, int) 298 */ 299 @Override 300 public void characters(char[] ch, int start, int length) throws SAXException { 301 try { 302 if (startElement!=null) { 303 out.write(startElement.toString()); 304 startElement=null; 305 } 306 /// mmm todo improve a bit 307 for (int i=start;i<(start+length);i++) { 308 char c = ch[i]; 309 out.write(c); 310 if (c=='\n') { 311 printedReturn=true; 312 continue; 313 } 314 } 315 //out.write(ch, start, length); 316 } catch (IOException e) { 317 throw new SAXException(e); 318 } 319 } 320 321 /** 322 * @see org.xml.sax.ContentHandler#ignorableWhitespace(char[], int, int) 323 */ 324 @Override 325 public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException { 326 try { 327 if (startElement!=null) { 328 out.write(startElement.toString()); 329 startElement=null; 330 } 331 out.write(ch, start, length); 332 } catch (IOException e) { 333 throw new SAXException(e); 334 } 335 } 336 337 /** 338 * @see org.xml.sax.ContentHandler#processingInstruction(java.lang.String, java.lang.String) 339 */ 340 @Override 341 public void processingInstruction(String target, String data) throws SAXException { 342 try { 343 indent(); 344 out.write("<?" + target + " " + data + "?>\r\n"); 345 } catch (IOException e) { 346 throw new SAXException(e); 347 } 348 } 349 350 /** 351 * @see org.xml.sax.ContentHandler#setDocumentLocator(org.xml.sax.Locator) 352 */ 353 @Override 354 public void setDocumentLocator(Locator locator) { 355 } 356 357 /** 358 * @see org.xml.sax.ContentHandler#skippedEntity(java.lang.String) 359 */ 360 @Override 361 public void skippedEntity(String name) throws SAXException { 362 // is for validating parser support, so not needed in xml writing. 363 } 364 365 /** 366 * @see org.xml.sax.ext.DefaultHandler2#comment(char[], int, int) 367 */ 368 @Override 369 public void comment(char[] ch, int start, int length) throws SAXException { 370 try { 371 indent(); 372 out.write("<!--"); 373 374 /// mmm todo improve a bit 375 for (int i=start;i<(start+length);i++) { 376 char c = ch[i]; 377 if (c=='\n') { 378 out.write(c); 379 indent(); 380 continue; 381 } 382 out.write(c); 383 } 384 out.write("-->"); 385 } catch (IOException e) { 386 throw new SAXException(e); 387 } 388 } 389 390 private void indent() throws IOException { 391 for (int i = 0; i < indent; i++) { 392 out.write('\t'); 393 } 394 } 395}