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