001    /*
002     $Id: MarkupBuilder.java,v 1.10 2005/07/05 20:11:42 glaforge 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 groovy.xml;
047    
048    import groovy.util.BuilderSupport;
049    import groovy.util.IndentPrinter;
050    
051    import java.io.PrintWriter;
052    import java.io.Writer;
053    import java.util.Iterator;
054    import java.util.Map;
055    
056    /**
057     * A helper class for creating XML or HTML markup
058     * 
059     * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
060     * @author Stefan Matthias Aust
061     * @author <a href="mailto:scottstirling@rcn.com">Scott Stirling</a>
062     * @version $Revision: 1.10 $
063     */
064    public class MarkupBuilder extends BuilderSupport {
065    
066        private IndentPrinter out;
067        private boolean nospace;
068        private int state;
069        private boolean nodeIsEmpty = true;
070    
071        public MarkupBuilder() {
072            this(new IndentPrinter());
073        }
074    
075        public MarkupBuilder(PrintWriter writer) {
076            this(new IndentPrinter(writer));
077        }
078    
079        public MarkupBuilder(Writer writer) {
080            this(new IndentPrinter(new PrintWriter(writer)));
081        }
082    
083        public MarkupBuilder(IndentPrinter out) {
084            this.out = out;
085        }
086    
087        protected IndentPrinter getPrinter() {
088            return this.out;
089        }
090    
091        protected void setParent(Object parent, Object child) {
092        }
093    
094        /*
095        public Object getProperty(String property) {
096            if (property.equals("_")) {
097                nospace = true;
098                return null;
099            } else {
100                Object node = createNode(property);
101                nodeCompleted(getCurrent(), node);
102                return node;
103            }
104        }
105        */
106    
107        protected Object createNode(Object name) {
108            toState(1, name);
109            return name;
110        }
111    
112        protected Object createNode(Object name, Object value) {
113            toState(2, name);
114            out.print(">");
115            out.print(transformValue(value.toString()));
116            return name;
117        }
118    
119        protected Object createNode(Object name, Map attributes, Object value) {
120            toState(1, name);
121            for (Iterator iter = attributes.entrySet().iterator(); iter.hasNext();) {
122                Map.Entry entry = (Map.Entry) iter.next();
123                out.print(" ");
124                print(transformName(entry.getKey().toString()));
125                out.print("='");
126                print(transformValue(entry.getValue().toString()));
127                out.print("'");
128            }
129            if (value != null)
130            {
131                nodeIsEmpty = false;
132                out.print(">" + transformValue(value.toString()) + "</" + name + ">");
133            }
134            return name;
135        }
136    
137        protected Object createNode(Object name, Map attributes) {
138            return createNode(name, attributes, null);
139        }
140        
141        protected void nodeCompleted(Object parent, Object node) {
142            toState(3, node);
143            out.flush();
144        }
145    
146        protected void print(Object node) {
147            out.print(node == null ? "null" : node.toString());
148        }
149    
150        protected Object getName(String methodName) {
151                    return super.getName(transformName(methodName));
152            }
153    
154        protected String transformName(String name) {
155            if (name.startsWith("_")) name = name.substring(1);
156            return name.replace('_', '-');
157        }
158    
159        /**
160         * Returns a String with special XML characters escaped as entities so that
161         * output XML is valid. Escapes the following characters as corresponding 
162         * entities:
163         * <ul>
164         *   <li>\' as &quot;</li>
165         *   <li>& as &amp;</li>
166         *   <li>< as &lt;</li>
167         *   <li>> as &gt;</li>
168         * </ul>
169         * 
170         * @param value to be searched and replaced for XML special characters.
171         * @return value with XML characters escaped
172         */
173        protected String transformValue(String value) {
174            // & has to be checked and replaced before others
175            if (value.matches(".*&.*")) {
176                value = value.replaceAll("&", "&");
177            }
178            if (value.matches(".*\\'.*")) {
179                value = value.replaceAll("\\'", """);
180            }
181            if (value.matches(".*<.*")) {
182                value = value.replaceAll("<", "<");
183            }
184            if (value.matches(".*>.*")) {
185                value = value.replaceAll(">", ">");
186            }
187            return value;
188        }
189    
190        private void toState(int next, Object name) {
191            switch (state) {
192                case 0:
193                    switch (next) {
194                        case 1:
195                        case 2:
196                            out.print("<");
197                            print(name);
198                            break;
199                        case 3:
200                            throw new Error();
201                    }
202                    break;
203                case 1:
204                    switch (next) {
205                        case 1:
206                        case 2:
207                            out.print(">");
208                            if (nospace) {
209                                nospace = false;
210                            } else {
211                                out.println();
212                                out.incrementIndent();
213                                out.printIndent();
214                            }
215                            out.print("<");
216                            print(name);
217                            break;
218                        case 3:
219                            if (nodeIsEmpty) {
220                                out.print(" />");
221                            }
222                            break;
223                    }
224                    break;
225                case 2:
226                    switch (next) {
227                        case 1:
228                        case 2:
229                            throw new Error();
230                        case 3:
231                            out.print("</");
232                            print(name);
233                            out.print(">");
234                            break;
235                    }
236                    break;
237                case 3:
238                    switch (next) {
239                        case 1:
240                        case 2:
241                            if (nospace) {
242                                nospace = false;
243                            } else {
244                                out.println();
245                                out.printIndent();
246                            }
247                            out.print("<");
248                            print(name);
249                            break;
250                        case 3:
251                            if (nospace) {
252                                nospace = false;
253                            } else {
254                                out.println();
255                                out.decrementIndent();
256                                out.printIndent();
257                            }
258                            out.print("</");
259                            print(name);
260                            out.print(">");
261                            break;
262                    }
263                    break;
264            }
265            state = next;
266        }
267    
268    }