001    /*
002     $Id: DomToGroovy.java 4530 2006-12-21 11:51:22Z paulk $
003    
004     Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
005    
006     Redistribution and use of this software and associated documentation
007     ("Software"), with or without modification, are permitted provided
008     that the following conditions are met:
009    
010     1. Redistributions of source code must retain copyright
011        statements and notices.  Redistributions must also contain a
012        copy of this document.
013    
014     2. Redistributions in binary form must reproduce the
015        above copyright notice, this list of conditions and the
016        following disclaimer in the documentation and/or other
017        materials provided with the distribution.
018    
019     3. The name "groovy" must not be used to endorse or promote
020        products derived from this Software without prior written
021        permission of The Codehaus.  For written permission,
022        please contact info@codehaus.org.
023    
024     4. Products derived from this Software may not be called "groovy"
025        nor may "groovy" appear in their names without prior written
026        permission of The Codehaus. "groovy" is a registered
027        trademark of The Codehaus.
028    
029     5. Due credit should be given to The Codehaus -
030        http://groovy.codehaus.org/
031    
032     THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS
033     ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
034     NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
035     FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
036     THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
037     INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
038     (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
039     SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
040     HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
041     STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
042     ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
043     OF THE POSSIBILITY OF SUCH DAMAGE.
044    
045     */
046    package org.codehaus.groovy.tools.xml;
047    
048    import groovy.util.IndentPrinter;
049    
050    import java.io.PrintWriter;
051    import java.util.HashMap;
052    import java.util.Map;
053    
054    import org.w3c.dom.Attr;
055    import org.w3c.dom.Comment;
056    import org.w3c.dom.Document;
057    import org.w3c.dom.Element;
058    import org.w3c.dom.NamedNodeMap;
059    import org.w3c.dom.Node;
060    import org.w3c.dom.NodeList;
061    import org.w3c.dom.ProcessingInstruction;
062    import org.w3c.dom.Text;
063    
064    /**
065     * A SAX handler for turning XML into Groovy scripts
066     * 
067     * @author James Strachan
068     * @author paulk
069     */
070    public class DomToGroovy {
071    
072        private IndentPrinter out;
073    
074        public DomToGroovy(PrintWriter out) {
075            this(new IndentPrinter(out));
076        }
077    
078        // TODO allow string quoting delimiter to be specified, e.g. ' vs "
079        public DomToGroovy(IndentPrinter out) {
080            this.out = out;
081        }
082    
083        public void print(Document document) {
084            printChildren(document, new HashMap());
085        }
086    
087        // Implementation methods
088        //-------------------------------------------------------------------------
089        protected void print(Node node, Map namespaces, boolean endWithComma) {
090            switch (node.getNodeType()) {
091                case Node.ELEMENT_NODE :
092                    printElement((Element) node, namespaces, endWithComma);
093                    break;
094                case Node.PROCESSING_INSTRUCTION_NODE :
095                    printPI((ProcessingInstruction) node, endWithComma);
096                    break;
097                case Node.TEXT_NODE :
098                    printText((Text) node, endWithComma);
099                    break;
100                case Node.COMMENT_NODE :
101                    printComment((Comment) node, endWithComma);
102                    break;
103            }
104        }
105    
106        protected void printElement(Element element, Map namespaces, boolean endWithComma) {
107            namespaces = defineNamespaces(element, namespaces);
108    
109            element.normalize();
110            printIndent();
111    
112            String prefix = element.getPrefix();
113            if (prefix != null && prefix.length() > 0) {
114                print(prefix);
115                print(".");
116            }
117            print(getLocalName(element));
118    
119            boolean hasAttributes = printAttributes(element);
120    
121            NodeList list = element.getChildNodes();
122            int length = list.getLength();
123            if (length == 0) {
124                printEnd(hasAttributes ? ")" : "()", endWithComma);
125            } else {
126                Node node = list.item(0);
127                if (length == 1 && node instanceof Text) {
128                    Text textNode = (Text) node;
129                    String text = getTextNodeData(textNode);
130                    if (hasAttributes) print(", '");
131                    else print("('");
132                    print(text);
133                    printEnd("')", endWithComma);
134                } else if (mixedContent(list)) {
135                    println(" [");
136                    out.incrementIndent();
137                    for (node = element.getFirstChild(); node != null; node = node.getNextSibling()) {
138                        boolean useComma = node.getNextSibling() != null;
139                        print(node, namespaces, useComma);
140                    }
141                    out.decrementIndent();
142                    printIndent();
143                    printEnd("]", endWithComma);
144                } else {
145                    println(") {");
146                    out.incrementIndent();
147                    printChildren(element, namespaces);
148                    out.decrementIndent();
149                    printIndent();
150                    printEnd("}", endWithComma);
151                }
152            }
153        }
154    
155        protected void printPI(ProcessingInstruction instruction, boolean endWithComma) {
156            printIndent();
157            print("xml.pi('");
158            print(instruction.getTarget());
159            print("', '");
160            print(instruction.getData());
161            printEnd("');", endWithComma);
162        }
163    
164        protected void printComment(Comment comment, boolean endWithComma) {
165            String text = comment.getData().trim();
166            if (text.length() >0) {
167                printIndent();
168                print("/* ");
169                print(text);
170                printEnd(" */", endWithComma);
171            }
172        }
173    
174        protected void printText(Text node, boolean endWithComma) {
175            String text = getTextNodeData(node);
176            if (text.length() > 0) {
177                printIndent();
178                //            print("xml.append('");
179                //            print(text);
180                //            println("');");
181                print("'");
182                print(text);
183                printEnd("'", endWithComma);
184            }
185        }
186    
187        protected Map defineNamespaces(Element element, Map namespaces) {
188            Map answer = null;
189            String prefix = element.getPrefix();
190            if (prefix != null && prefix.length() > 0 && !namespaces.containsKey(prefix)) {
191                answer = new HashMap(namespaces);
192                defineNamespace(answer, prefix, element.getNamespaceURI());
193            }
194            NamedNodeMap attributes = element.getAttributes();
195            int length = attributes.getLength();
196            for (int i = 0; i < length; i++) {
197                Attr attribute = (Attr) attributes.item(i);
198                prefix = attribute.getPrefix();
199                if (prefix != null && prefix.length() > 0 && !namespaces.containsKey(prefix)) {
200                    if (answer == null) {
201                        answer = new HashMap(namespaces);
202                    }
203                    defineNamespace(answer, prefix, attribute.getNamespaceURI());
204                }
205            }
206            return (answer != null) ? answer : namespaces;
207        }
208    
209        protected void defineNamespace(Map namespaces, String prefix, String uri) {
210            namespaces.put(prefix, uri);
211            if (!prefix.equals("xmlns") && !prefix.equals("xml")) {
212                printIndent();
213                print(prefix);
214                print(" = xmlns.namespace('");
215                print(uri);
216                println("')");
217            }
218        }
219    
220        protected boolean printAttributes(Element element) {
221            boolean hasAttribute = false;
222            NamedNodeMap attributes = element.getAttributes();
223            int length = attributes.getLength();
224            if (length > 0) {
225                StringBuffer buffer = new StringBuffer();
226                for (int i = 0; i < length; i++) {
227                    printAttributeWithPrefix((Attr) attributes.item(i), buffer);
228                }
229                print("(");
230                for (int i = 0; i < length; i++) {
231                    hasAttribute = printAttributeWithoutPrefix((Attr) attributes.item(i), hasAttribute);
232                }
233                if (buffer.length() > 0) {
234                    if (hasAttribute) {
235                        print(", ");
236                    }
237                    print("xmlns=[");
238                    print(buffer.toString());
239                    print("]");
240                    hasAttribute = true;
241                }
242            }
243            return hasAttribute;
244        }
245    
246        private void printAttributeWithPrefix(Attr attribute, StringBuffer buffer) {
247            String prefix = attribute.getPrefix();
248            if (prefix != null && prefix.length() > 0) {
249                if (buffer.length() > 0) {
250                    buffer.append(", ");
251                }
252                buffer.append(prefix);
253                buffer.append(".");
254                buffer.append(getLocalName(attribute));
255                buffer.append(":'");
256                buffer.append(getAttributeValue(attribute));
257                buffer.append("'");
258            }
259        }
260    
261        private String getAttributeValue(Attr attribute) {
262            return attribute.getValue();
263        }
264    
265        private boolean printAttributeWithoutPrefix(Attr attribute, boolean hasAttribute) {
266            String prefix = attribute.getPrefix();
267            if (prefix == null || prefix.length() == 0) {
268                if (!hasAttribute) {
269                    hasAttribute = true;
270                } else {
271                    print(", ");
272                }
273                print(getLocalName(attribute));
274                print(":'");
275                print(getAttributeValue(attribute));
276                print("'");
277            }
278            return hasAttribute;
279        }
280    
281        protected String getTextNodeData(Text node) {
282            return node.getData().trim();
283        }
284    
285        protected boolean mixedContent(NodeList list) {
286            boolean hasText = false;
287            boolean hasElement = false;
288            for (int i = 0, size = list.getLength(); i < size; i++) {
289                Node node = list.item(i);
290                if (node instanceof Element) {
291                    hasElement = true;
292                } else if (node instanceof Text) {
293                    String text = getTextNodeData((Text) node);
294                    if (text.length() > 0) {
295                        hasText = true;
296                    }
297                }
298            }
299            return hasText && hasElement;
300        }
301    
302        protected void printChildren(Node parent, Map namespaces) {
303            for (Node node = parent.getFirstChild(); node != null; node = node.getNextSibling()) {
304                print(node, namespaces, false);
305            }
306        }
307    
308        protected String getLocalName(Node node) {
309            String answer = node.getLocalName();
310            if (answer == null) {
311                answer = node.getNodeName();
312            }
313            return answer.trim();
314        }
315    
316        protected void printEnd(String text, boolean endWithComma) {
317            if (endWithComma) {
318                print(text);
319                println(",");
320            } else {
321                println(text);
322            }
323        }
324    
325        protected void println(String text) {
326            out.println(text);
327        }
328    
329        protected void print(String text) {
330            out.print(text);
331        }
332    
333        protected void printIndent() {
334            out.printIndent();
335        }
336    }