View Javadoc

1   /*
2    $Id: MarkupBuilder.java,v 1.10 2005/07/05 20:11:42 glaforge Exp $
3   
4    Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
5   
6    Redistribution and use of this software and associated documentation
7    ("Software"), with or without modification, are permitted provided
8    that the following conditions are met:
9   
10   1. Redistributions of source code must retain copyright
11      statements and notices.  Redistributions must also contain a
12      copy of this document.
13  
14   2. Redistributions in binary form must reproduce the
15      above copyright notice, this list of conditions and the
16      following disclaimer in the documentation and/or other
17      materials provided with the distribution.
18  
19   3. The name "groovy" must not be used to endorse or promote
20      products derived from this Software without prior written
21      permission of The Codehaus.  For written permission,
22      please contact info@codehaus.org.
23  
24   4. Products derived from this Software may not be called "groovy"
25      nor may "groovy" appear in their names without prior written
26      permission of The Codehaus. "groovy" is a registered
27      trademark of The Codehaus.
28  
29   5. Due credit should be given to The Codehaus -
30      http://groovy.codehaus.org/
31  
32   THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS
33   ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
34   NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
35   FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
36   THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
37   INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
38   (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
39   SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
40   HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
41   STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
42   ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
43   OF THE POSSIBILITY OF SUCH DAMAGE.
44  
45   */
46  package groovy.xml;
47  
48  import groovy.util.BuilderSupport;
49  import groovy.util.IndentPrinter;
50  
51  import java.io.PrintWriter;
52  import java.io.Writer;
53  import java.util.Iterator;
54  import java.util.Map;
55  
56  /***
57   * A helper class for creating XML or HTML markup
58   * 
59   * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
60   * @author Stefan Matthias Aust
61   * @author <a href="mailto:scottstirling@rcn.com">Scott Stirling</a>
62   * @version $Revision: 1.10 $
63   */
64  public class MarkupBuilder extends BuilderSupport {
65  
66      private IndentPrinter out;
67      private boolean nospace;
68      private int state;
69      private boolean nodeIsEmpty = true;
70  
71      public MarkupBuilder() {
72          this(new IndentPrinter());
73      }
74  
75      public MarkupBuilder(PrintWriter writer) {
76          this(new IndentPrinter(writer));
77      }
78  
79      public MarkupBuilder(Writer writer) {
80          this(new IndentPrinter(new PrintWriter(writer)));
81      }
82  
83      public MarkupBuilder(IndentPrinter out) {
84          this.out = out;
85      }
86  
87      protected IndentPrinter getPrinter() {
88          return this.out;
89      }
90  
91      protected void setParent(Object parent, Object child) {
92      }
93  
94      /*
95      public Object getProperty(String property) {
96          if (property.equals("_")) {
97              nospace = true;
98              return null;
99          } 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 &amp;quot;</li>
165      *   <li>&amp; as &amp;amp;</li>
166      *   <li>&lt; as &amp;lt;</li>
167      *   <li>&gt; as &amp;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("&", "&amp;");
177         }
178         if (value.matches(".*//'.*")) {
179             value = value.replaceAll("//'", "&quot;");
180         }
181         if (value.matches(".*<.*")) {
182             value = value.replaceAll("<", "&lt;");
183         }
184         if (value.matches(".*>.*")) {
185             value = value.replaceAll(">", "&gt;");
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 }