Xml2Groovy.java
001 /*
002  * Copyright 2008-2016 the original author or authors.
003  *
004  * Licensed under the Apache License, Version 2.0 (the "License");
005  * you may not use this file except in compliance with the License.
006  * You may obtain a copy of the License at
007  *
008  *     http://www.apache.org/licenses/LICENSE-2.0
009  *
010  * Unless required by applicable law or agreed to in writing, software
011  * distributed under the License is distributed on an "AS IS" BASIS,
012  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013  * See the License for the specific language governing permissions and
014  * limitations under the License.
015  */
016 package griffon.util;
017 
018 import groovy.util.IndentPrinter;
019 import groovy.util.XmlSlurper;
020 import groovy.util.slurpersupport.GPathResult;
021 import groovy.util.slurpersupport.Node;
022 import groovy.util.slurpersupport.NodeChild;
023 import org.codehaus.groovy.runtime.DefaultGroovyMethods;
024 import org.xml.sax.InputSource;
025 import org.xml.sax.SAXException;
026 
027 import javax.xml.parsers.ParserConfigurationException;
028 import java.io.ByteArrayOutputStream;
029 import java.io.File;
030 import java.io.IOException;
031 import java.io.InputStream;
032 import java.io.OutputStream;
033 import java.io.OutputStreamWriter;
034 import java.io.PrintWriter;
035 import java.io.Reader;
036 import java.util.ArrayList;
037 import java.util.Iterator;
038 import java.util.List;
039 import java.util.Map;
040 
041 /**
042  * Translates an XML file into a Groovy script that is suitable for a Groovy builder.
043  * String literals must be escaped either using single or double quotes. <p>
044  * This helper class is useful for translating an XML View definition into a Groovy
045  * script that can be handled by an UberBuilder, for example this View
046  *
047  <xmp>
048     <application title="app.config.application.title"
049                  pack="true">
050         <actions>
051             <action id="'clickAction'"
052                     name="'Click'"
053                     closure="{controller.click(it)}"/>
054         </actions>
055     
056         <gridLayout cols="1" rows="3"/>
057         <textField id="'input'" columns="20"
058             text="bind('value', target: model)"/>
059         <textField id="'output'" columns="20"
060             text="bind{model.value}" editable="false"/>
061         <button action="clickAction"/>
062     </application>
063  </xmp>
064  *
065  * results in the following script
066  *
067  <pre>
068 application(title: app.config.application.title, pack: true) {
069   actions {
070     action(id: 'clickAction', name: 'Click', closure: {controller.click(it)})
071   }
072   gridLayout(cols: 1, rows: 3)
073   textField(id: 'input', text: bind('value', target: model), columns: 20)
074   textField(id: 'output', text: bind{model.value}, columns: 20, editable: false)
075   button(action: clickAction)
076 }
077  </pre>
078  *
079  @author Andres Almiray
080  */
081 public final class Xml2Groovy {
082     private static final Xml2Groovy INSTANCE;
083 
084     static {
085         INSTANCE = new Xml2Groovy();
086     }
087 
088     public static Xml2Groovy getInstance() {
089         return INSTANCE;
090     }
091 
092     private Xml2Groovy() {
093 
094     }
095 
096     public String parse(File file) {
097         try {
098             return translate(newXmlSlurper().parse(file));
099         catch (IOException | SAXException e) {
100             throw new IllegalArgumentException(e);
101         }
102     }
103 
104     public String parse(InputSource source) {
105         try {
106             return translate(newXmlSlurper().parse(source));
107         catch (IOException | SAXException e) {
108             throw new IllegalArgumentException(e);
109         }
110     }
111 
112     public String parse(InputStream stream) {
113         try {
114             return translate(newXmlSlurper().parse(stream));
115         catch (IOException | SAXException e) {
116             throw new IllegalArgumentException(e);
117         }
118     }
119 
120     public String parse(Reader reader) {
121         try {
122             return translate(newXmlSlurper().parse(reader));
123         catch (IOException | SAXException e) {
124             throw new IllegalArgumentException(e);
125         }
126     }
127 
128     public String parse(String uri) {
129         try {
130             return translate(newXmlSlurper().parse(uri));
131         catch (IOException | SAXException e) {
132             throw new IllegalArgumentException(e);
133         }
134     }
135 
136     public String parse(GPathResult root) {
137         return translate(root);
138     }
139 
140     public String parseText(String text) {
141         try {
142             return translate(newXmlSlurper().parseText(text));
143         catch (IOException | SAXException e) {
144             throw new IllegalArgumentException(e);
145         }
146     }
147 
148     private XmlSlurper newXmlSlurper() {
149         try {
150             return new XmlSlurper();
151         catch (ParserConfigurationException | SAXException e) {
152             throw new IllegalArgumentException(e);
153         }
154     }
155 
156     private String translate(GPathResult root) {
157         ByteArrayOutputStream baos = new ByteArrayOutputStream();
158         IndentPrinter printer = createIndentPrinter(baos);
159         walkXml(printer, (NodeChildroot);
160         printer.flush();
161 
162         return baos.toString();
163     }
164 
165     private IndentPrinter createIndentPrinter(OutputStream os) {
166         PrintWriter pw = new PrintWriter(new OutputStreamWriter(os));
167         return new IndentPrinter(pw, "    ");
168     }
169 
170     private void walkXml(IndentPrinter printer, NodeChild node) {
171         printer.printIndent();
172         printer.print(node.name());
173         if (!node.attributes().isEmpty()) {
174             printer.print("(");
175             List<String> attrs = new ArrayList<>();
176             for (Object o : node.attributes().entrySet()) {
177                 Map.Entry<?, ?> entry = (Map.Entry<?, ?>o;
178                 attrs.add(entry.getKey() ": " + entry.getValue());
179             }
180             printer.print(DefaultGroovyMethods.join((Iterableattrs, ", "));
181             printer.print(")");
182         }
183 
184         if (node.children().size() 0) {
185             printer.println(" {");
186             printer.incrementIndent();
187             for (Iterator<?> iter = node.childNodes(); iter.hasNext()) {
188                 Object child = iter.next();
189                 if (child instanceof NodeChild) {
190                     walkXml(printer, (NodeChildchild);
191                 else if (child instanceof Node) {
192                     walkXml(printer, (Nodechild);
193                 }
194             }
195             printer.decrementIndent();
196             printer.printIndent();
197             printer.println("}");
198         else if (!node.attributes().isEmpty()) {
199             printer.println("");
200         else {
201             printer.println("()");
202         }
203     }
204 
205     private void walkXml(IndentPrinter printer, Node node) {
206         printer.printIndent();
207         printer.print(node.name());
208         if (!node.attributes().isEmpty()) {
209             printer.print("(");
210             List<String> attrs = new ArrayList<>();
211             for (Object o : node.attributes().entrySet()) {
212                 Map.Entry<?, ?> entry = (Map.Entry<?, ?>o;
213                 attrs.add(entry.getKey() ": " + entry.getValue());
214             }
215             printer.print(DefaultGroovyMethods.join((Iterableattrs, ", "));
216             printer.print(")");
217         }
218 
219         if (node.children().size() 0) {
220             printer.println(" {");
221             printer.incrementIndent();
222             for (Iterator<?> iter = node.childNodes(); iter.hasNext()) {
223                 Object child = iter.next();
224                 if (child instanceof NodeChild) {
225                     walkXml(printer, (NodeChildchild);
226                 else if (child instanceof Node) {
227                     walkXml(printer, (Nodechild);
228                 }
229             }
230             printer.decrementIndent();
231             printer.printIndent();
232             printer.println("}");
233         else if (!node.attributes().isEmpty()) {
234             printer.println("");
235         else {
236             printer.println("()");
237         }
238     }
239 }