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