1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.commons.configuration;
19
20 import java.io.File;
21 import java.io.PrintWriter;
22 import java.io.Reader;
23 import java.io.Writer;
24 import java.net.URL;
25 import java.util.Iterator;
26 import java.util.List;
27 import javax.xml.parsers.SAXParser;
28 import javax.xml.parsers.SAXParserFactory;
29
30 import org.apache.commons.lang.StringEscapeUtils;
31 import org.apache.commons.lang.StringUtils;
32
33 import org.xml.sax.Attributes;
34 import org.xml.sax.EntityResolver;
35 import org.xml.sax.InputSource;
36 import org.xml.sax.XMLReader;
37 import org.xml.sax.helpers.DefaultHandler;
38
39 /***
40 * This configuration implements the XML properties format introduced in Java
41 * 5.0, see http://java.sun.com/j2se/1.5.0/docs/api/java/util/Properties.html.
42 * An XML properties file looks like this:
43 *
44 * <pre>
45 * <?xml version="1.0"?>
46 * <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
47 * <properties>
48 * <comment>Description of the property list</comment>
49 * <entry key="key1">value1</entry>
50 * <entry key="key2">value2</entry>
51 * <entry key="key3">value3</entry>
52 * </properties>
53 * </pre>
54 *
55 * The Java 5.0 runtime is not required to use this class. The default encoding
56 * for this configuration format is UTF-8. Note that unlike
57 * <code>PropertiesConfiguration</code>, <code>XMLPropertiesConfiguration</code>
58 * does not support includes.
59 *
60 * @author Emmanuel Bourg
61 * @author Alistair Young
62 * @version $Revision: 439648 $, $Date: 2006-09-02 22:42:10 +0200 (Sa, 02 Sep 2006) $
63 * @since 1.1
64 */
65 public class XMLPropertiesConfiguration extends PropertiesConfiguration
66 {
67 /***
68 * The default encoding (UTF-8 as specified by http://java.sun.com/j2se/1.5.0/docs/api/java/util/Properties.html)
69 */
70 private static final String DEFAULT_ENCODING = "UTF-8";
71
72
73 {
74 setEncoding(DEFAULT_ENCODING);
75 }
76
77 /***
78 * Creates an empty XMLPropertyConfiguration object which can be
79 * used to synthesize a new Properties file by adding values and
80 * then saving(). An object constructed by this C'tor can not be
81 * tickled into loading included files because it cannot supply a
82 * base for relative includes.
83 */
84 public XMLPropertiesConfiguration()
85 {
86 super();
87 }
88
89 /***
90 * Creates and loads the xml properties from the specified file.
91 * The specified file can contain "include" properties which then
92 * are loaded and merged into the properties.
93 *
94 * @param fileName The name of the properties file to load.
95 * @throws ConfigurationException Error while loading the properties file
96 */
97 public XMLPropertiesConfiguration(String fileName) throws ConfigurationException
98 {
99 super(fileName);
100 }
101
102 /***
103 * Creates and loads the xml properties from the specified file.
104 * The specified file can contain "include" properties which then
105 * are loaded and merged into the properties.
106 *
107 * @param file The properties file to load.
108 * @throws ConfigurationException Error while loading the properties file
109 */
110 public XMLPropertiesConfiguration(File file) throws ConfigurationException
111 {
112 super(file);
113 }
114
115 /***
116 * Creates and loads the xml properties from the specified URL.
117 * The specified file can contain "include" properties which then
118 * are loaded and merged into the properties.
119 *
120 * @param url The location of the properties file to load.
121 * @throws ConfigurationException Error while loading the properties file
122 */
123 public XMLPropertiesConfiguration(URL url) throws ConfigurationException
124 {
125 super(url);
126 }
127
128 public void load(Reader in) throws ConfigurationException
129 {
130 SAXParserFactory factory = SAXParserFactory.newInstance();
131 factory.setNamespaceAware(false);
132 factory.setValidating(true);
133
134 try
135 {
136 SAXParser parser = factory.newSAXParser();
137
138 XMLReader xmlReader = parser.getXMLReader();
139 xmlReader.setEntityResolver(new EntityResolver()
140 {
141 public InputSource resolveEntity(String publicId, String systemId)
142 {
143 return new InputSource(getClass().getClassLoader().getResourceAsStream("properties.dtd"));
144 }
145 });
146 xmlReader.setContentHandler(new XMLPropertiesHandler());
147 xmlReader.parse(new InputSource(in));
148 }
149 catch (Exception e)
150 {
151 throw new ConfigurationException("Unable to parse the configuration file", e);
152 }
153
154
155 }
156
157 public void save(Writer out) throws ConfigurationException
158 {
159 PrintWriter writer = new PrintWriter(out);
160
161 String encoding = getEncoding() != null ? getEncoding() : DEFAULT_ENCODING;
162 writer.println("<?xml version=\"1.0\" encoding=\"" + encoding + "\"?>");
163 writer.println("<!DOCTYPE properties SYSTEM \"http://java.sun.com/dtd/properties.dtd\">");
164 writer.println("<properties>");
165
166 if (getHeader() != null)
167 {
168 writer.println(" <comment>" + StringEscapeUtils.escapeXml(getHeader()) + "</comment>");
169 }
170
171 Iterator keys = getKeys();
172 while (keys.hasNext())
173 {
174 String key = (String) keys.next();
175 Object value = getProperty(key);
176
177 if (value instanceof List)
178 {
179 writeProperty(writer, key, (List) value);
180 }
181 else
182 {
183 writeProperty(writer, key, value);
184 }
185 }
186
187 writer.println("</properties>");
188 writer.flush();
189 }
190
191 /***
192 * Write a property.
193 *
194 * @param out the output stream
195 * @param key the key of the property
196 * @param value the value of the property
197 */
198 private void writeProperty(PrintWriter out, String key, Object value)
199 {
200
201 String k = StringEscapeUtils.escapeXml(key);
202
203 if (value != null)
204 {
205
206 String v = StringEscapeUtils.escapeXml(String.valueOf(value));
207 v = StringUtils.replace(v, String.valueOf(getListDelimiter()), "//" + getListDelimiter());
208
209 out.println(" <entry key=\"" + k + "\">" + v + "</entry>");
210 }
211 else
212 {
213 out.println(" <entry key=\"" + k + "\"/>");
214 }
215 }
216
217 /***
218 * Write a list property.
219 *
220 * @param out the output stream
221 * @param key the key of the property
222 * @param values a list with all property values
223 */
224 private void writeProperty(PrintWriter out, String key, List values)
225 {
226 for (int i = 0; i < values.size(); i++)
227 {
228 writeProperty(out, key, values.get(i));
229 }
230 }
231
232 /***
233 * SAX Handler to parse a XML properties file.
234 *
235 * @author Alistair Young
236 * @since 1.2
237 */
238 private class XMLPropertiesHandler extends DefaultHandler
239 {
240 /*** The key of the current entry being parsed. */
241 private String key;
242
243 /*** The value of the current entry being parsed. */
244 private StringBuffer value = new StringBuffer();
245
246 /*** Indicates that a comment is being parsed. */
247 private boolean inCommentElement;
248
249 /*** Indicates that an entry is being parsed. */
250 private boolean inEntryElement;
251
252 public void startElement(String uri, String localName, String qName, Attributes attrs)
253 {
254 if ("comment".equals(qName))
255 {
256 inCommentElement = true;
257 }
258
259 if ("entry".equals(qName))
260 {
261 key = attrs.getValue("key");
262 inEntryElement = true;
263 }
264 }
265
266 public void endElement(String uri, String localName, String qName)
267 {
268 if (inCommentElement)
269 {
270
271 setHeader(value.toString());
272 inCommentElement = false;
273 }
274
275 if (inEntryElement)
276 {
277
278 addProperty(key, value.toString());
279 inEntryElement = false;
280 }
281
282
283 value = new StringBuffer();
284 }
285
286 public void characters(char[] chars, int start, int length)
287 {
288 /***
289 * We're currently processing an element. All character data from now until
290 * the next endElement() call will be the data for this element.
291 */
292 value.append(chars, start, length);
293 }
294 }
295 }