1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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.11 $
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
96
97
98
99
100
101
102
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(escapeElementContent(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(escapeAttributeValue(entry.getValue().toString()));
127 out.print("'");
128 }
129 if (value != null)
130 {
131 nodeIsEmpty = false;
132 out.print(">" + escapeElementContent(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 &apos;</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
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 /***
191 * Escapes a string so that it can be used directly as an XML
192 * attribute value.
193 * @param value The string to escape.
194 * @return A new string in which all characters that require escaping
195 * have been replaced with the corresponding XML entities.
196 * @see #escapeXmlValue(String, boolean)
197 */
198 private String escapeAttributeValue(String value) {
199 return escapeXmlValue(value, true);
200 }
201
202 /***
203 * Escapes a string so that it can be used directly in XML element
204 * content.
205 * @param value The string to escape.
206 * @return A new string in which all characters that require escaping
207 * have been replaced with the corresponding XML entities.
208 * @see #escapeXmlValue(String, boolean)
209 */
210 private String escapeElementContent(String value) {
211 return escapeXmlValue(value, false);
212 }
213
214 /***
215 * Escapes a string so that it can be used in XML text successfully.
216 * It replaces the following characters with the corresponding XML
217 * entities:
218 * <ul>
219 * <li>& as &amp;</li>
220 * <li>< as &lt;</li>
221 * <li>> as &gt;</li>
222 * </ul>
223 * If the string is to be added as an attribute value, these
224 * characters are also escaped:
225 * <ul>
226 * <li>' as &apos;</li>
227 * </ul>
228 * @param value The string to escape.
229 * @param isAttrValue <code>true</code> if the string is to be used
230 * as an attribute value, otherwise <code>false</code>.
231 * @return A new string in which all characters that require escaping
232 * have been replaced with the corresponding XML entities.
233 */
234 private String escapeXmlValue(String value, boolean isAttrValue){
235
236 if (value.matches(".*&.*")) {
237 value = value.replaceAll("&", "&");
238 }
239 if (value.matches(".*<.*")) {
240 value = value.replaceAll("<", "<");
241 }
242 if (value.matches(".*>.*")) {
243 value = value.replaceAll(">", ">");
244 }
245 if (isAttrValue){
246
247
248 if (value.matches(".*//'.*")) {
249 value = value.replaceAll("//'", "'");
250 }
251 }
252 return value;
253 }
254
255 private void toState(int next, Object name) {
256 switch (state) {
257 case 0:
258 switch (next) {
259 case 1:
260 case 2:
261 out.print("<");
262 print(name);
263 break;
264 case 3:
265 throw new Error();
266 }
267 break;
268 case 1:
269 switch (next) {
270 case 1:
271 case 2:
272 out.print(">");
273 if (nospace) {
274 nospace = false;
275 } else {
276 out.println();
277 out.incrementIndent();
278 out.printIndent();
279 }
280 out.print("<");
281 print(name);
282 break;
283 case 3:
284 if (nodeIsEmpty) {
285 out.print(" />");
286 }
287 break;
288 }
289 break;
290 case 2:
291 switch (next) {
292 case 1:
293 case 2:
294 throw new Error();
295 case 3:
296 out.print("</");
297 print(name);
298 out.print(">");
299 break;
300 }
301 break;
302 case 3:
303 switch (next) {
304 case 1:
305 case 2:
306 if (nospace) {
307 nospace = false;
308 } else {
309 out.println();
310 out.printIndent();
311 }
312 out.print("<");
313 print(name);
314 break;
315 case 3:
316 if (nospace) {
317 nospace = false;
318 } else {
319 out.println();
320 out.decrementIndent();
321 out.printIndent();
322 }
323 out.print("</");
324 print(name);
325 out.print(">");
326 break;
327 }
328 break;
329 }
330 state = next;
331 }
332
333 }