001/* 002// This software is subject to the terms of the Eclipse Public License v1.0 003// Agreement, available at the following URL: 004// http://www.eclipse.org/legal/epl-v10.html. 005// You must accept the terms of that agreement to use this software. 006// 007// Copyright (C) 2005-2013 Pentaho 008// All Rights Reserved. 009*/ 010package mondrian.xmla.impl; 011 012import mondrian.olap.Util; 013import mondrian.util.ArrayStack; 014import mondrian.xmla.SaxWriter; 015 016import org.eigenbase.xom.XMLUtil; 017import org.eigenbase.xom.XOMUtil; 018 019import org.xml.sax.Attributes; 020 021import java.io.*; 022import java.util.regex.Pattern; 023 024/** 025 * Default implementation of {@link SaxWriter}. 026 * 027 * @author jhyde 028 * @author Gang Chen 029 * @since 27 April, 2003 030 */ 031public class DefaultSaxWriter implements SaxWriter { 032 /** Inside the tag of an element. */ 033 private static final int STATE_IN_TAG = 0; 034 /** After the tag at the end of an element. */ 035 private static final int STATE_END_ELEMENT = 1; 036 /** After the tag at the start of an element. */ 037 private static final int STATE_AFTER_TAG = 2; 038 /** After a burst of character data. */ 039 private static final int STATE_CHARACTERS = 3; 040 041 private final PrintWriter writer; 042 private int indent; 043 private String indentStr = " "; 044 private final ArrayStack<String> stack = new ArrayStack<String>(); 045 private int state = STATE_END_ELEMENT; 046 047 private static final Pattern nlPattern = Pattern.compile("\\r\\n|\\r|\\n"); 048 049 /** 050 * Creates a DefaultSaxWriter writing to an {@link java.io.OutputStream}. 051 */ 052 public DefaultSaxWriter(OutputStream stream) { 053 this(new OutputStreamWriter(stream)); 054 } 055 056 public DefaultSaxWriter(OutputStream stream, String xmlEncoding) 057 throws UnsupportedEncodingException 058 { 059 this(new OutputStreamWriter(stream, xmlEncoding)); 060 } 061 062 /** 063 * Creates a <code>SAXWriter</code> writing to a {@link java.io.Writer}. 064 * 065 * <p>If <code>writer</code> is a {@link java.io.PrintWriter}, 066 * {@link #DefaultSaxWriter(java.io.OutputStream)} is preferred. 067 */ 068 public DefaultSaxWriter(Writer writer) { 069 this(new PrintWriter(writer), 0); 070 } 071 072 /** 073 * Creates a DefaultSaxWriter writing to a {@link java.io.PrintWriter}. 074 * 075 * @param writer Writer 076 * @param initialIndent Initial indent 077 */ 078 public DefaultSaxWriter(PrintWriter writer, int initialIndent) { 079 this.writer = writer; 080 this.indent = initialIndent; 081 } 082 083 private void _startElement( 084 String namespaceURI, 085 String localName, 086 String qName, 087 Attributes atts) 088 { 089 _checkTag(); 090 if (indent > 0) { 091 writer.println(); 092 } 093 for (int i = 0; i < indent; i++) { 094 writer.write(indentStr); 095 } 096 indent++; 097 writer.write('<'); 098 writer.write(qName); 099 for (int i = 0; i < atts.getLength(); i++) { 100 XMLUtil.printAtt(writer, atts.getQName(i), atts.getValue(i)); 101 } 102 state = STATE_IN_TAG; 103 } 104 105 private void _checkTag() { 106 if (state == STATE_IN_TAG) { 107 state = STATE_AFTER_TAG; 108 writer.print(">"); 109 } 110 } 111 112 private void _endElement( 113 String namespaceURI, 114 String localName, 115 String qName) 116 { 117 indent--; 118 if (state == STATE_IN_TAG) { 119 writer.write("/>"); 120 } else { 121 if (state != STATE_CHARACTERS) { 122 writer.println(); 123 for (int i = 0; i < indent; i++) { 124 writer.write(indentStr); 125 } 126 } 127 writer.write("</"); 128 writer.write(qName); 129 writer.write('>'); 130 } 131 state = STATE_END_ELEMENT; 132 } 133 134 private void _characters(char ch[], int start, int length) { 135 _checkTag(); 136 137 // Display the string, quoting in <![CDATA[ ... ]]> if necessary, 138 // or using XML escapes as a last result. 139 String s = new String(ch, start, length); 140 if (XOMUtil.stringHasXMLSpecials(s)) { 141 XMLUtil.stringEncodeXML(s, writer); 142/* 143 if (s.indexOf("]]>") < 0) { 144 writer.print("<![CDATA["); 145 writer.print(s); 146 writer.print("]]>"); 147 } else { 148 XMLUtil.stringEncodeXML(s, writer); 149 } 150*/ 151 } else { 152 writer.print(s); 153 } 154 155 state = STATE_CHARACTERS; 156 } 157 158 159 // 160 // Simplifying methods 161 162 public void characters(String s) { 163 if (s != null && s.length() > 0) { 164 _characters(s.toCharArray(), 0, s.length()); 165 } 166 } 167 168 public void startSequence(String name, String subName) { 169 if (name != null) { 170 startElement(name); 171 } else { 172 stack.push(null); 173 } 174 } 175 176 public void endSequence() { 177 if (stack.peek() == null) { 178 stack.pop(); 179 } else { 180 endElement(); 181 } 182 } 183 184 public final void textElement(String name, Object data) { 185 startElement(name); 186 187 // Replace line endings with spaces. IBM's DOM implementation keeps 188 // line endings, whereas Sun's does not. For consistency, always strip 189 // them. 190 // 191 // REVIEW: It would be better to enclose in CDATA, but some clients 192 // might not be expecting this. 193 characters( 194 nlPattern.matcher(data.toString()) 195 .replaceAll(" ")); 196 endElement(); 197 } 198 199 public void element(String tagName, Object... attributes) { 200 startElement(tagName, attributes); 201 endElement(); 202 } 203 204 public void startElement(String tagName) { 205 _startElement(null, null, tagName, EmptyAttributes); 206 stack.add(tagName); 207 } 208 209 public void startElement(String tagName, Object... attributes) { 210 _startElement(null, null, tagName, new StringAttributes(attributes)); 211 assert tagName != null; 212 stack.add(tagName); 213 } 214 215 public void endElement() { 216 String tagName = stack.pop(); 217 _endElement(null, null, tagName); 218 } 219 220 public void startDocument() { 221 if (stack.size() != 0) { 222 throw new IllegalStateException("Document already started"); 223 } 224 } 225 226 public void endDocument() { 227 if (stack.size() != 0) { 228 throw new IllegalStateException( 229 "Document may have unbalanced elements"); 230 } 231 writer.flush(); 232 } 233 234 public void completeBeforeElement(String tagName) { 235 if (stack.indexOf(tagName) == -1) { 236 return; 237 } 238 239 String currentTagName = stack.peek(); 240 while (!tagName.equals(currentTagName)) { 241 _endElement(null, null, currentTagName); 242 stack.pop(); 243 currentTagName = stack.peek(); 244 } 245 } 246 247 public void verbatim(String text) { 248 _checkTag(); 249 writer.print(text); 250 } 251 252 public void flush() { 253 writer.flush(); 254 } 255 256 private static final Attributes EmptyAttributes = new Attributes() { 257 public int getLength() { 258 return 0; 259 } 260 261 public String getURI(int index) { 262 return null; 263 } 264 265 public String getLocalName(int index) { 266 return null; 267 } 268 269 public String getQName(int index) { 270 return null; 271 } 272 273 public String getType(int index) { 274 return null; 275 } 276 277 public String getValue(int index) { 278 return null; 279 } 280 281 public int getIndex(String uri, String localName) { 282 return 0; 283 } 284 285 public int getIndex(String qName) { 286 return 0; 287 } 288 289 public String getType(String uri, String localName) { 290 return null; 291 } 292 293 public String getType(String qName) { 294 return null; 295 } 296 297 public String getValue(String uri, String localName) { 298 return null; 299 } 300 301 public String getValue(String qName) { 302 return null; 303 } 304 }; 305 306 /** 307 * List of SAX attributes based upon a string array. 308 */ 309 public static class StringAttributes implements Attributes { 310 private final Object[] strings; 311 312 public StringAttributes(Object[] strings) { 313 this.strings = strings; 314 } 315 316 public int getLength() { 317 return strings.length / 2; 318 } 319 320 public String getURI(int index) { 321 return null; 322 } 323 324 public String getLocalName(int index) { 325 return null; 326 } 327 328 public String getQName(int index) { 329 return (String) strings[index * 2]; 330 } 331 332 public String getType(int index) { 333 return null; 334 } 335 336 public String getValue(int index) { 337 return stringValue(strings[index * 2 + 1]); 338 } 339 340 public int getIndex(String uri, String localName) { 341 return -1; 342 } 343 344 public int getIndex(String qName) { 345 final int count = strings.length / 2; 346 for (int i = 0; i < count; i++) { 347 String string = (String) strings[i * 2]; 348 if (string.equals(qName)) { 349 return i; 350 } 351 } 352 return -1; 353 } 354 355 public String getType(String uri, String localName) { 356 return null; 357 } 358 359 public String getType(String qName) { 360 return null; 361 } 362 363 public String getValue(String uri, String localName) { 364 return null; 365 } 366 367 public String getValue(String qName) { 368 final int index = getIndex(qName); 369 if (index < 0) { 370 return null; 371 } else { 372 return stringValue(strings[index * 2 + 1]); 373 } 374 } 375 376 private static String stringValue(Object s) { 377 return s == null ? null : s.toString(); 378 } 379 } 380} 381 382// End DefaultSaxWriter.java