001    /*
002     $Id: DomToGroovy.java,v 1.5 2003/11/04 12:00:48 jstrachan Exp $
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 <a href="mailto:james@coredevelopers.net">James Strachan</a>
068     * @version $Revision: 1.5 $
069     */
070    public class DomToGroovy {
071    
072        private IndentPrinter out;
073    
074        public DomToGroovy(PrintWriter out) {
075            this(new IndentPrinter(out));
076        }
077    
078        public DomToGroovy(IndentPrinter out) {
079            this.out = out;
080        }
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("", endWithComma);
125            }
126            else {
127                Node node = list.item(0);
128                if (length == 1 && node instanceof Text) {
129                    Text textNode = (Text) node;
130                    String text = getTextNodeData(textNode);
131                    if (hasAttributes) {
132                        print(" [\"");
133                        print(text);
134                        printEnd("\"]", endWithComma);
135                    }
136                    else {
137                        print("(\"");
138                        print(text);
139                        printEnd("\")", endWithComma);
140                    }
141                }
142                else if (mixedContent(list)) {
143                    println(" [");
144                    out.incrementIndent();
145                    for (node = element.getFirstChild(); node != null; node = node.getNextSibling()) {
146                        boolean useComma = node.getNextSibling() != null;
147                        print(node, namespaces, useComma);
148                    }
149                    out.decrementIndent();
150                    printIndent();
151                    printEnd("]", endWithComma);
152                }
153                else {
154                    println(" {");
155                    out.incrementIndent();
156                    printChildren(element, namespaces);
157                    out.decrementIndent();
158                    printIndent();
159                    printEnd("}", endWithComma);
160                }
161            }
162        }
163    
164        protected void printPI(ProcessingInstruction instruction, boolean endWithComma) {
165            printIndent();
166            print("xml.pi('");
167            print(instruction.getTarget());
168            print("', '");
169            print(instruction.getData());
170            printEnd("');", endWithComma);
171        }
172    
173        protected void printComment(Comment comment, boolean endWithComma) {
174            String text = comment.getData().trim();
175            if (text.length() >0) {
176                printIndent();
177                print("/* ");
178                print(text);
179                printEnd(" */", endWithComma);
180            }
181        }
182    
183        protected void printText(Text node, boolean endWithComma) {
184            String text = getTextNodeData(node);
185            if (text.length() > 0) {
186                printIndent();
187                //            print("xml.append('");
188                //            print(text);
189                //            println("');");
190                print("\"");
191                print(text);
192                printEnd("\"", endWithComma);
193            }
194        }
195    
196        protected Map defineNamespaces(Element element, Map namespaces) {
197            Map answer = null;
198            String prefix = element.getPrefix();
199            if (prefix != null && prefix.length() > 0 && !namespaces.containsKey(prefix)) {
200                answer = new HashMap(namespaces);
201                defineNamespace(answer, prefix, element.getNamespaceURI());
202            }
203            NamedNodeMap attributes = element.getAttributes();
204            int length = attributes.getLength();
205            for (int i = 0; i < length; i++) {
206                Attr attribute = (Attr) attributes.item(i);
207                prefix = attribute.getPrefix();
208                if (prefix != null && prefix.length() > 0 && !namespaces.containsKey(prefix)) {
209                    if (answer == null) {
210                        answer = new HashMap(namespaces);
211                    }
212                    defineNamespace(answer, prefix, attribute.getNamespaceURI());
213                }
214            }
215            return (answer != null) ? answer : namespaces;
216        }
217    
218        protected void defineNamespace(Map namespaces, String prefix, String uri) {
219            namespaces.put(prefix, uri);
220            if (!prefix.equals("xmlns") && !prefix.equals("xml")) {
221                printIndent();
222                print(prefix);
223                print(" = xmlns.namespace('");
224                print(uri);
225                println("')");
226            }
227        }
228    
229        protected boolean printAttributes(Element element) {
230            boolean hasAttribute = false;
231    
232            NamedNodeMap attributes = element.getAttributes();
233            int length = attributes.getLength();
234            if (length > 0) {
235                StringBuffer buffer = new StringBuffer();
236                for (int i = 0; i < length; i++) {
237                    Attr attribute = (Attr) attributes.item(i);
238                    String prefix = attribute.getPrefix();
239                    if (prefix != null && prefix.length() > 0) {
240                        if (buffer.length() > 0) {
241                            buffer.append(", ");
242                        }
243                        buffer.append(prefix);
244                        buffer.append(".");
245                        buffer.append(getLocalName(attribute));
246                        buffer.append(":'");
247                        buffer.append(attribute.getValue());
248                        buffer.append("'");
249                    }
250                }
251    
252                print("(");
253                for (int i = 0; i < length; i++) {
254                    Attr attribute = (Attr) attributes.item(i);
255                    String prefix = attribute.getPrefix();
256                    if (prefix == null || prefix.length() == 0) {
257                        if (!hasAttribute) {
258                            hasAttribute = true;
259                        }
260                        else {
261                            print(", ");
262                        }
263                        print(getLocalName(attribute));
264                        print(":'");
265                        print(attribute.getValue());
266                        print("'");
267                    }
268                }
269                if (buffer.length() > 0) {
270                    if (hasAttribute) {
271                        print(", ");
272                    }
273                    print("xmlns=[");
274                    print(buffer.toString());
275                    print("]");
276                    hasAttribute = true;
277                }
278                print(")");
279            }
280            return hasAttribute;
281        }
282    
283        protected String getTextNodeData(Text node) {
284            String text = node.getData().trim();
285            return text;
286        }
287    
288        protected boolean mixedContent(NodeList list) {
289            boolean hasText = false;
290            boolean hasElement = false;
291            for (int i = 0, size = list.getLength(); i < size; i++) {
292                Node node = list.item(i);
293                if (node instanceof Element) {
294                    hasElement = true;
295                }
296                else if (node instanceof Text) {
297                    String text = getTextNodeData((Text) node);
298                    if (text.length() > 0) {
299                        hasText = true;
300                    }
301                }
302            }
303            return hasText && hasElement;
304        }
305    
306        protected void printChildren(Node parent, Map namespaces) {
307            for (Node node = parent.getFirstChild(); node != null; node = node.getNextSibling()) {
308                print(node, namespaces, false);
309            }
310        }
311    
312        protected String getLocalName(Node node) {
313            String answer = node.getLocalName();
314            if (answer == null) {
315                answer = node.getNodeName();
316            }
317            return answer.trim();
318        }
319    
320        protected void printEnd(String text, boolean endWithComma) {
321            if (endWithComma) {
322                print(text);
323                println(",");
324            }
325            else {
326                println(text);
327            }
328        }
329    
330        protected void println(String text) {
331            out.println(text);    }
332    
333        protected void print(String text) {
334            out.print(text);
335        }
336    
337        protected void printIndent() {
338            out.printIndent();
339        }
340    }