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) 2002-2005 Julian Hyde
008// Copyright (C) 2005-2009 Pentaho and others
009// All Rights Reserved.
010*/
011package mondrian.web.taglib;
012
013import mondrian.olap.*;
014
015import org.apache.log4j.Logger;
016
017import org.w3c.dom.*;
018
019import java.io.*;
020import java.util.List;
021import javax.xml.parsers.*;
022import javax.xml.transform.Templates;
023import javax.xml.transform.TransformerFactory;
024import javax.xml.transform.dom.DOMSource;
025import javax.xml.transform.stream.StreamResult;
026import javax.xml.transform.stream.StreamSource;
027
028/**
029 * Transforms a mondrian result into a DOM (Document Object Model).
030 *
031 * @author Andreas Voss, 22 March, 2002
032 */
033public class DomBuilder {
034    private static final Logger LOGGER = Logger.getLogger(DomBuilder.class);
035
036    Document factory;
037    Result result;
038    int dimCount;
039
040    protected DomBuilder(Document factory, Result result) {
041        this.factory = factory;
042        this.result = result;
043    }
044
045    public static Document build(Result result)
046        throws ParserConfigurationException
047    {
048        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
049        dbf.setValidating(false);
050        dbf.setExpandEntityReferences(true);
051        DocumentBuilder db = dbf.newDocumentBuilder();
052        Document doc = db.newDocument();
053        Element table = build(doc, result);
054        doc.appendChild(table);
055        // debug(doc);
056        return doc;
057    }
058
059    public static Element build(Document factory, Result result) {
060        return new DomBuilder(factory, result).build();
061    }
062
063    private Element build() {
064        dimCount = result.getAxes().length;
065        Element mdxtable = factory.createElement("mdxtable");
066        Element query = elem("query", mdxtable);
067        cdata(Util.unparse(result.getQuery()), query);
068        Element head = elem("head", mdxtable);
069        Element body = elem("body", mdxtable);
070        switch (dimCount) {
071        case 0:
072            buildRows0Dim(body);
073            break;
074        case 1:
075            buildColumns(head, result.getAxes()[0]);
076            buildRows1Dim(body);
077            break;
078        case 2:
079            buildColumns(head, result.getAxes()[0]);
080            buildRows2Dim(body, result.getAxes()[1]);
081            break;
082        default:
083            throw new IllegalArgumentException(
084                "DomBuilder requires 0, 1 or 2 dimensional result");
085        }
086        Element slicers = elem("slicers", mdxtable);
087        buildSlicer(slicers);
088        return mdxtable;
089    }
090
091    abstract class AxisBuilder {
092        Member[] prevMembers;
093        Element[] prevElems;
094        int [] prevSpan;
095
096        Element parent;
097        List<Position> positions;
098        int levels;
099
100        AxisBuilder(Element parent, Axis axis) {
101            this.parent   = parent;
102
103            positions = axis.getPositions();
104            levels = positions.get(0).size();
105            prevMembers = new Member[levels];
106            prevElems = new Element[levels];
107            prevSpan = new int[levels];
108        }
109
110        abstract int getRowCount();
111        abstract Element build(int rowIndex);
112    }
113
114    class RowBuilder extends AxisBuilder {
115        RowBuilder(Element parent, Axis axis) {
116            super(parent, axis);
117        }
118
119        Element build(int rowIndex) {
120            boolean even = (rowIndex % 2 != 0);  // counting starts at row 1
121            Element row = elem("row", parent);
122            build(row, positions.get(rowIndex), even);
123            return row;
124        }
125
126        int getRowCount() {
127            return positions.size();
128        }
129
130        private void build(
131            Element row,
132            List<Member> currentMembers,
133            boolean even)
134        {
135            for (int i = 0; i < levels; i++) {
136                Member currentMember = currentMembers.get(i);
137                Member prevMember    = prevMembers[i];
138                if (prevMember == null || !prevMember.equals(currentMember)) {
139                    Element currentElem =
140                        createMemberElem("row-heading", row, currentMember);
141                    if (even) {
142                        currentElem.setAttribute("style", "even");
143                    } else {
144                        currentElem.setAttribute("style", "odd");
145                    }
146                    prevMembers[i] = currentMember;
147                    prevElems[i] = currentElem;
148                    prevSpan[i] = 1;
149                    for (int j = i + 1; j < levels; j++) {
150                        prevMembers[j] = null;
151                    }
152                } else {
153                    Element prevElem = prevElems[i];
154                    prevElem.setAttribute("style", "span");
155                    prevSpan[i] += 1;
156                    prevElem.setAttribute(
157                        "rowspan", Integer.toString(prevSpan[i]));
158                }
159            }
160        }
161    }
162
163
164    class ColumnBuilder extends AxisBuilder {
165        ColumnBuilder(Element parent, Axis axis) {
166            super(parent, axis);
167        }
168
169        int getRowCount() {
170            return levels;
171        }
172
173        Element build(int rowIndex) {
174            Element row = elem("row", parent);
175            if (dimCount > 1 && rowIndex == 0) {
176                buildCornerElement(row);
177            }
178            build(row, rowIndex);
179            return row;
180        }
181
182        private void build(Element row, int rowIndex) {
183            for (int i = 0; i < levels; i++) {
184                prevMembers[i] = null;
185            }
186            for (int i = 0; i < positions.size(); i++) {
187                Position position = positions.get(i);
188                //Member[] currentMembers = positions.get(i).getMembers();
189
190                for (int j = 0; j < rowIndex - 1; j++) {
191                    Member currentMember = position.get(j);
192                    if (prevMembers[j] == null
193                        || !prevMembers[j].equals(currentMember))
194                    {
195                        prevMembers[j] = currentMember;
196                        for (int k = j + 1; k < levels; k++) {
197                            prevMembers[j] = null;
198                        }
199                    }
200                }
201
202                Member currentMember = position.get(rowIndex);
203                Member prevMember    = prevMembers[rowIndex];
204                if (prevMember == null || !prevMember.equals(currentMember)) {
205                    Element currentElem =
206                        createMemberElem("column-heading", row, currentMember);
207                    prevMembers[rowIndex] = currentMember;
208                    prevElems[rowIndex] = currentElem;
209                    prevSpan[rowIndex] = 1;
210                    for (int j = rowIndex + 1; j < levels; j++) {
211                        prevMembers[j] = null;
212                    }
213                } else {
214                    Element prevElem = prevElems[rowIndex];
215                    prevElem.setAttribute("style", "span");
216                    prevSpan[rowIndex] += 1;
217                    prevElem.setAttribute(
218                        "colspan", Integer.toString(prevSpan[rowIndex]));
219                }
220            }
221        }
222
223        void buildCornerElement(Element row) {
224            Element corner = elem("corner", row);
225            corner.setAttribute(
226                "rowspan",
227                Integer.toString(
228                    result.getAxes()[0].getPositions().get(0).size()));
229            corner.setAttribute(
230                "colspan",
231                Integer.toString(
232                    result.getAxes()[1].getPositions().get(0).size()));
233        }
234    }
235
236
237    private void buildRows2Dim(Element parent, Axis axis) {
238        RowBuilder rb = new RowBuilder(parent, axis);
239        final int N = rb.getRowCount();
240        int[] cellIndex = new int[2];
241        for (int i = 0; i < N; i++) {
242            Element row = rb.build(i);
243            boolean even = (i % 2 != 0);  // counting starts at row 1
244            cellIndex[1] = i;
245            buildCells(row, cellIndex, even);
246        }
247    }
248
249    private void buildRows1Dim(Element parent) {
250        int[] cellIndex = new int[1];
251        Element row = elem("row", parent);
252        buildCells(row, cellIndex, false);
253    }
254
255    private void buildColumns(Element parent, Axis axis) {
256        ColumnBuilder cb = new ColumnBuilder(parent, axis);
257        final int N = cb.getRowCount();
258        for (int i = 0; i < N; i++) {
259            Element row = cb.build(i);
260        }
261    }
262
263
264    private void buildCells(Element row, int[] cellIndex, boolean even) {
265        int columns = result.getAxes()[0].getPositions().size();
266        for (int i = 0; i < columns; i++) {
267            cellIndex[0] = i;
268            Cell cell = result.getCell(cellIndex);
269            buildCell(cell, row, even);
270        }
271    }
272
273    private void buildCell(Cell cell, Element row, boolean even) {
274        Element cellElem = elem("cell", row);
275        String s = cell.getFormattedValue();
276        if (s == null || s.length() == 0 || s.equals("(null)")) {
277            s = "\u00a0"; // &nbsp;
278        }
279        cellElem.setAttribute("value", s);
280        cellElem.setAttribute("style", even ? "even" : "odd");
281    }
282
283    private void buildRows0Dim(Element parent) {
284        int[] cellIndex = new int[0];
285        Element row = elem("row", parent);
286        Cell cell = result.getCell(cellIndex);
287        buildCell(cell, row, false);
288    }
289
290    private void buildSlicer(Element parent) {
291        List<Position> positions = result.getSlicerAxis().getPositions();
292        for (int i = 0; i < positions.size(); i++) {
293            Position position = positions.get(i);
294            if (position.size() > 0) {
295                Element el = elem("position", parent);
296                for (int j = 0; j < position.size(); j++) {
297                    createMemberElem("member", el, position.get(j));
298                }
299            }
300        }
301    }
302
303    private Element createMemberElem(String name, Element parent, Member m) {
304        Element e = elem(name, parent);
305        e.setAttribute("caption", m.getCaption());
306        e.setAttribute("depth", Integer.toString(m.getLevel().getDepth()));
307        //e.setAttribute("name", m.getName());
308        //e.setAttribute("qname", m.getQualifiedName());
309        e.setAttribute("uname", m.getUniqueName());
310        e.setAttribute("colspan", "1");
311        e.setAttribute("rowspan", "1");
312
313        // add properties to dom tree
314        addMemberProperties(m, e);
315
316        return e;
317    }
318
319    private void addMemberProperties(Member m, Element e) {
320        Property[] props = m.getLevel().getProperties();
321        if (props != null) {
322            for (int i = 0; i < props.length; i++) {
323                String propName = props[i].getName();
324                String propValue = "" + m.getPropertyValue(propName);
325                Element propElem = elem("property", e);
326                propElem.setAttribute("name", propName);
327                propElem.setAttribute("value", propValue);
328            }
329        }
330    }
331
332    private Element elem(String name, Element parent) {
333        Element elem = factory.createElement(name);
334        parent.appendChild(elem);
335        return elem;
336    }
337
338    private Object cdata(String content, Element parent) {
339        CDATASection section = factory.createCDATASection(content);
340        parent.appendChild(section);
341        return section;
342    }
343
344    private static final String PRETTY_PRINTER =
345        ""
346        + "<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\">\n"
347        + "<xsl:output method=\"xml\" indent=\"yes\"/>\n"
348        + "<xsl:template match=\"*|@*\">\n"
349        + "  <xsl:copy>\n"
350        + "    <xsl:apply-templates select=\"*|@*\"/>\n"
351        + "  </xsl:copy>\n"
352        + "</xsl:template>\n"
353        + "</xsl:stylesheet>\n";
354
355    public static void debug(Document doc) {
356        try {
357            TransformerFactory tf = TransformerFactory.newInstance();
358            StringReader input = new StringReader(PRETTY_PRINTER);
359            Templates templates = tf.newTemplates(new StreamSource(input));
360            OutputStream result = new ByteArrayOutputStream();
361            templates.newTransformer().transform(
362                new DOMSource(doc),
363                new StreamResult(result));
364            LOGGER.debug(result.toString());
365        } catch (Exception e) {
366            e.printStackTrace();
367        }
368    }
369
370}
371
372// End DomBuilder.java