SAXWriter
writing to a {@link java.io.Writer}.
+ * Creates a DefaultSaxWriter without indentation.
*
- * If Initially, a StringEscaper starts out as
- * an identity transform in the "mutable" state. Call defineEscape as many
- * times as necessary to set up mappings, and then call makeImmutable() before
- * using appendEscapedString to actually apply the defined transform. Or, use
- * one of the global mappings pre-defined here. To create a StringEscaper, create a builder.
+ * Initially it is the identity transform (it leaves every character unchanged).
+ * Call {@link Builder#defineEscape} as many
+ * times as necessary to set up mappings, and then call {@link Builder#build}
+ * to create a StringEscaper. StringEscaper is immutable, but you can call {@link #toBuilder()} to
+ * get a builder back. Several escapers are pre-defined:writer
is a {@link java.io.PrintWriter},
- * {@link #DefaultSaxWriter(java.io.OutputStream)} is preferred.
+ * @param buf String builder to write to
*/
- public DefaultSaxWriter(Writer writer) {
- this(new PrintWriter(writer), 0);
+ public DefaultSaxWriter(Appendable buf) {
+ this(buf, 0);
}
/**
- * Creates a DefaultSaxWriter writing to a {@link java.io.PrintWriter}.
+ * Creates a DefaultSaxWriter.
*
- * @param writer
- * @param initialIndent
+ * @param buf String builder to write to
+ * @param initialIndent Initial indent (0 to write on single line)
*/
- public DefaultSaxWriter(PrintWriter writer, int initialIndent) {
- this.writer = writer;
+ public DefaultSaxWriter(Appendable buf, int initialIndent) {
+ this.buf = buf;
this.indent = initialIndent;
}
@@ -81,84 +78,76 @@ private void _startElement(
String namespaceURI,
String localName,
String qName,
- Attributes atts)
+ Attributes atts) throws IOException
{
_checkTag();
if (indent > 0) {
- writer.println();
+ buf.append(Util.nl);
}
for (int i = 0; i < indent; i++) {
- writer.write(indentStr);
+ buf.append(indentStr);
}
indent++;
- writer.write('<');
- writer.write(qName);
- for (int i = 0; i < atts.getLength(); i++) {
- XmlUtil.printAtt(writer, atts.getQName(i), atts.getValue(i));
+ buf.append('<');
+ buf.append(qName);
+ final int length = atts.getLength();
+ for (int i = 0; i < length; i++) {
+ String val = atts.getValue(i);
+ if (val != null) {
+ buf.append(' ');
+ buf.append(atts.getQName(i));
+ buf.append("=\"");
+ StringEscaper.XML_NUMERIC_ESCAPER.appendEscapedString(val, buf);
+ buf.append("\"");
+ }
}
state = STATE_IN_TAG;
+ assert qName != null;
+ stack.add(qName);
}
- private void _checkTag() {
+ private void _checkTag() throws IOException {
if (state == STATE_IN_TAG) {
state = STATE_AFTER_TAG;
- writer.print(">");
+ buf.append('>');
}
}
- private void _endElement(
- String namespaceURI,
- String localName,
- String qName)
+ private void _endElement() throws IOException
{
+ String qName = stack.pop();
indent--;
if (state == STATE_IN_TAG) {
- writer.write("/>");
+ buf.append("/>");
} else {
if (state != STATE_CHARACTERS) {
- writer.println();
+ buf.append(Util.nl);
for (int i = 0; i < indent; i++) {
- writer.write(indentStr);
+ buf.append(indentStr);
}
}
- writer.write("");
- writer.write(qName);
- writer.write('>');
+ buf.append("");
+ buf.append(qName);
+ buf.append('>');
}
state = STATE_END_ELEMENT;
}
- private void _characters(char ch[], int start, int length) {
+ private void _characters(String s) throws IOException
+ {
_checkTag();
-
- // Display the string, quoting in if necessary,
- // or using XML escapes as a last result.
- String s = new String(ch, start, length);
- if (XmlUtil.stringHasXmlSpecials(s)) {
- XmlUtil.stringEncodeXml(s, writer);
-/*
- if (s.indexOf("]]>") < 0) {
- writer.print("");
- } else {
- XMLUtil.stringEncodeXml(s, writer);
- }
-*/
- } else {
- writer.print(s);
- }
-
+ StringEscaper.XML_NUMERIC_ESCAPER.appendEscapedString(s, buf);
state = STATE_CHARACTERS;
}
-
//
// Simplifying methods
public void characters(String s) {
- if (s != null && s.length() > 0) {
- _characters(s.toCharArray(), 0, s.length());
+ try {
+ _characters(s);
+ } catch (IOException e) {
+ throw new RuntimeException("Error while appending XML", e);
}
}
@@ -179,18 +168,24 @@ public void endSequence() {
}
public final void textElement(String name, Object data) {
- startElement(name);
- String s = data.toString();
-
- // Replace line endings with spaces. IBM's DOM implementation keeps
- // line endings, whereas Sun's does not. For consistency, always strip
- // them.
- //
- // REVIEW: It would be better to enclose in CDATA, but some clients
- // might not be expecting this.
- s = Util.replace(s, Util.nl, " ");
- characters(s);
- endElement();
+ try {
+ _startElement(null, null, name, EmptyAttributes);
+ String s = data.toString();
+
+ // Replace line endings with spaces. IBM's DOM implementation keeps
+ // line endings, whereas Sun's does not. For consistency, always
+ // strip them.
+ //
+ // REVIEW: It would be better to enclose in CDATA, but some clients
+ // might not be expecting this.
+ if (s != null && s.length() > 0) {
+ s = Util.replace(s, Util.nl, " ");
+ _characters(s);
+ }
+ _endElement();
+ } catch (IOException e) {
+ throw new RuntimeException("Error while appending XML", e);
+ }
}
public void element(String tagName, Object... attributes) {
@@ -199,19 +194,28 @@ public void element(String tagName, Object... attributes) {
}
public void startElement(String tagName) {
- _startElement(null, null, tagName, EmptyAttributes);
- stack.add(tagName);
+ try {
+ _startElement(null, null, tagName, EmptyAttributes);
+ } catch (IOException e) {
+ throw new RuntimeException("Error while appending XML", e);
+ }
}
public void startElement(String tagName, Object... attributes) {
- _startElement(null, null, tagName, new StringAttributes(attributes));
- assert tagName != null;
- stack.add(tagName);
+ try {
+ _startElement(
+ null, null, tagName, new StringAttributes(attributes));
+ } catch (IOException e) {
+ throw new RuntimeException("Error while appending XML", e);
+ }
}
public void endElement() {
- String tagName = stack.pop();
- _endElement(null, null, tagName);
+ try {
+ _endElement();
+ } catch (IOException e) {
+ throw new RuntimeException("Error while appending XML", e);
+ }
}
public void startDocument() {
@@ -225,7 +229,7 @@ public void endDocument() {
throw new IllegalStateException(
"Document may have unbalanced elements");
}
- writer.flush();
+ flush();
}
public void completeBeforeElement(String tagName) {
@@ -235,19 +239,32 @@ public void completeBeforeElement(String tagName) {
String currentTagName = stack.peek();
while (!tagName.equals(currentTagName)) {
- _endElement(null, null, currentTagName);
- stack.pop();
+ try {
+ _endElement();
+ } catch (IOException e) {
+ throw new RuntimeException("Error while appending XML", e);
+ }
currentTagName = stack.peek();
}
}
public void verbatim(String text) {
- _checkTag();
- writer.print(text);
+ try {
+ _checkTag();
+ buf.append(text);
+ } catch (IOException e) {
+ throw new RuntimeException("Error while appending XML", e);
+ }
}
public void flush() {
- writer.flush();
+ if (buf instanceof Writer) {
+ try {
+ ((Writer) buf).flush();
+ } catch (IOException e) {
+ throw new RuntimeException("Error while flushing XML", e);
+ }
+ }
}
private static final Attributes EmptyAttributes = new Attributes() {
diff --git a/src/main/java/mondrian/xmla/impl/DefaultXmlaServlet.java b/src/main/java/mondrian/xmla/impl/DefaultXmlaServlet.java
index 07cade1..9a42d60 100644
--- a/src/main/java/mondrian/xmla/impl/DefaultXmlaServlet.java
+++ b/src/main/java/mondrian/xmla/impl/DefaultXmlaServlet.java
@@ -555,13 +555,11 @@ protected void marshallSoapMessage(
break;
}
- /*
- * The setCharacterEncoding, setContentType, or setLocale method
- * must be called BEFORE getWriter or getOutputStream and before
- * committing the response for the character encoding to be used.
- *
- * @see javax.servlet.ServletResponse
- */
+ // The setCharacterEncoding, setContentType, or setLocale method
+ // must be called BEFORE getWriter or getOutputStream and before
+ // committing the response for the character encoding to be used.
+ //
+ // See javax.servlet.ServletResponse
OutputStream outputStream = response.getOutputStream();
diff --git a/src/main/java/org/olap4j/xmla/server/impl/Composite.java b/src/main/java/org/olap4j/xmla/server/impl/Composite.java
index 1ecdea0..6616dee 100644
--- a/src/main/java/org/olap4j/xmla/server/impl/Composite.java
+++ b/src/main/java/org/olap4j/xmla/server/impl/Composite.java
@@ -28,7 +28,7 @@ public abstract class Composite {
public static
+ *
*/
-public class StringEscaper implements Cloneable
+public class StringEscaper
{
- private ArrayList translationVector;
- private String [] translationTable;
-
- public static StringEscaper xmlEscaper;
- public static StringEscaper xmlNumericEscaper;
- public static StringEscaper htmlEscaper;
- public static StringEscaper urlArgEscaper;
- public static StringEscaper urlEscaper;
-
- /**
- * Identity transform
- */
- public StringEscaper()
- {
- translationVector = new ArrayList();
- }
-
- /**
- * Map character "from" to escape sequence "to"
- */
- public void defineEscape(char from,String to)
- {
- int i = (int) from;
- if (i >= translationVector.size()) {
- // Extend list by adding the requisite number of nulls.
- final int count = i + 1 - translationVector.size();
- translationVector.addAll(
- new AbstractList() {
- public Object get(int index) {
- return null;
- }
-
- public int size() {
- return count;
- }
- });
- }
- translationVector.set(i, to);
- }
+ private final String [] translationTable;
+
+ public static final StringEscaper HTML_ESCAPER =
+ new Builder()
+ .defineEscape('&', "&")
+ .defineEscape('"', """)
+ .defineEscape('\'', "'") // not "'"
+ .defineEscape('<', "<")
+ .defineEscape('>', ">")
+ .build();
+
+ public static final StringEscaper XML_ESCAPER = HTML_ESCAPER;
+
+ public static final StringEscaper XML_NUMERIC_ESCAPER =
+ new Builder()
+ .defineEscape('&',"&")
+ .defineEscape('"',""")
+ .defineEscape('\'',"'")
+ .defineEscape('<',"<")
+ .defineEscape('>',">")
+ .defineEscape('\t'," ")
+ .defineEscape('\n',"
")
+ .defineEscape('\r',"
")
+ .build();
+
+ public static final StringEscaper URL_ARG_ESCAPER =
+ new Builder()
+ .defineEscape('?', "%3f")
+ .defineEscape('&', "%26")
+ .build();
+
+ public static final StringEscaper URL_ESCAPER =
+ URL_ARG_ESCAPER.toBuilder()
+ .defineEscape('%', "%%")
+ .defineEscape('"', "%22")
+ .defineEscape('\r', "+")
+ .defineEscape('\n', "+")
+ .defineEscape(' ', "+")
+ .defineEscape('#', "%23")
+ .build();
/**
- * Call this before attempting to escape strings; after this,
- * defineEscape may not be called again.
+ * Creates a StringEscaper. Only called from Builder.
*/
- public void makeImmutable()
- {
- translationTable =
- (String[]) translationVector.toArray(
- new String[translationVector.size()]);
- translationVector = null;
+ private StringEscaper(String[] translationTable) {
+ this.translationTable = translationTable;
}
/**
@@ -88,7 +103,7 @@ public void makeImmutable()
*/
public String escapeString(String s)
{
- StringBuffer sb = null;
+ StringBuilder sb = null;
int n = s.length();
for (int i = 0; i < n; i++) {
char c = s.charAt(i);
@@ -107,7 +122,7 @@ public String escapeString(String s)
}
} else {
if (sb == null) {
- sb = new StringBuffer(n * 2);
+ sb = new StringBuilder(n * 2);
sb.append(s.substring(0, i));
}
sb.append(escape);
@@ -122,7 +137,7 @@ public String escapeString(String s)
}
/**
- * Apply an immutable transformation to the given string, writing the
+ * Applies an immutable transformation to the given string, writing the
* results to a string buffer.
*/
public void appendEscapedString(String s, StringBuffer sb)
@@ -144,68 +159,79 @@ public void appendEscapedString(String s, StringBuffer sb)
}
}
- protected Object clone()
+ /**
+ * Applies an immutable transformation to the given string, writing the
+ * results to an {@link Appendable} (such as a {@link StringBuilder}).
+ */
+ public void appendEscapedString(String s, Appendable sb) throws IOException
{
- StringEscaper clone = new StringEscaper();
- if (translationVector != null) {
- clone.translationVector = new ArrayList(translationVector);
- }
- if (translationTable != null) {
- clone.translationTable = (String[]) translationTable.clone();
+ int n = s.length();
+ for (int i = 0; i < n; i++) {
+ char c = s.charAt(i);
+ String escape;
+ if (c >= translationTable.length) {
+ escape = null;
+ } else {
+ escape = translationTable[c];
+ }
+ if (escape == null) {
+ sb.append(c);
+ } else {
+ sb.append(escape);
+ }
}
- return clone;
}
/**
- * Create a mutable escaper from an existing escaper, which may
- * already be immutable.
+ * Creates a builder from an existing escaper.
*/
- public StringEscaper getMutableClone()
+ public Builder toBuilder()
{
- StringEscaper clone = (StringEscaper) clone();
- if (clone.translationVector == null) {
- clone.translationVector =
- new ArrayList(Arrays.asList(clone.translationTable));
- clone.translationTable = null;
- }
- return clone;
+ return new Builder(
+ new ArrayList