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