001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: package org.apache.commons.configuration;
019:
020: import java.io.File;
021: import java.io.PrintWriter;
022: import java.io.Reader;
023: import java.io.Writer;
024: import java.net.URL;
025: import java.util.Iterator;
026: import java.util.List;
027: import javax.xml.parsers.SAXParser;
028: import javax.xml.parsers.SAXParserFactory;
029:
030: import org.apache.commons.lang.StringEscapeUtils;
031: import org.apache.commons.lang.StringUtils;
032:
033: import org.xml.sax.Attributes;
034: import org.xml.sax.EntityResolver;
035: import org.xml.sax.InputSource;
036: import org.xml.sax.XMLReader;
037: import org.xml.sax.helpers.DefaultHandler;
038:
039: /**
040: * This configuration implements the XML properties format introduced in Java
041: * 5.0, see http://java.sun.com/j2se/1.5.0/docs/api/java/util/Properties.html.
042: * An XML properties file looks like this:
043: *
044: * <pre>
045: * <?xml version="1.0"?>
046: * <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
047: * <properties>
048: * <comment>Description of the property list</comment>
049: * <entry key="key1">value1</entry>
050: * <entry key="key2">value2</entry>
051: * <entry key="key3">value3</entry>
052: * </properties>
053: * </pre>
054: *
055: * The Java 5.0 runtime is not required to use this class. The default encoding
056: * for this configuration format is UTF-8. Note that unlike
057: * <code>PropertiesConfiguration</code>, <code>XMLPropertiesConfiguration</code>
058: * does not support includes.
059: *
060: * @author Emmanuel Bourg
061: * @author Alistair Young
062: * @version $Revision: 439648 $, $Date: 2006-09-02 22:42:10 +0200 (Sa, 02 Sep 2006) $
063: * @since 1.1
064: */
065: public class XMLPropertiesConfiguration extends PropertiesConfiguration {
066: /**
067: * The default encoding (UTF-8 as specified by http://java.sun.com/j2se/1.5.0/docs/api/java/util/Properties.html)
068: */
069: private static final String DEFAULT_ENCODING = "UTF-8";
070:
071: // initialization block to set the encoding before loading the file in the constructors
072: {
073: setEncoding(DEFAULT_ENCODING);
074: }
075:
076: /**
077: * Creates an empty XMLPropertyConfiguration object which can be
078: * used to synthesize a new Properties file by adding values and
079: * then saving(). An object constructed by this C'tor can not be
080: * tickled into loading included files because it cannot supply a
081: * base for relative includes.
082: */
083: public XMLPropertiesConfiguration() {
084: super ();
085: }
086:
087: /**
088: * Creates and loads the xml properties from the specified file.
089: * The specified file can contain "include" properties which then
090: * are loaded and merged into the properties.
091: *
092: * @param fileName The name of the properties file to load.
093: * @throws ConfigurationException Error while loading the properties file
094: */
095: public XMLPropertiesConfiguration(String fileName)
096: throws ConfigurationException {
097: super (fileName);
098: }
099:
100: /**
101: * Creates and loads the xml properties from the specified file.
102: * The specified file can contain "include" properties which then
103: * are loaded and merged into the properties.
104: *
105: * @param file The properties file to load.
106: * @throws ConfigurationException Error while loading the properties file
107: */
108: public XMLPropertiesConfiguration(File file)
109: throws ConfigurationException {
110: super (file);
111: }
112:
113: /**
114: * Creates and loads the xml properties from the specified URL.
115: * The specified file can contain "include" properties which then
116: * are loaded and merged into the properties.
117: *
118: * @param url The location of the properties file to load.
119: * @throws ConfigurationException Error while loading the properties file
120: */
121: public XMLPropertiesConfiguration(URL url)
122: throws ConfigurationException {
123: super (url);
124: }
125:
126: public void load(Reader in) throws ConfigurationException {
127: SAXParserFactory factory = SAXParserFactory.newInstance();
128: factory.setNamespaceAware(false);
129: factory.setValidating(true);
130:
131: try {
132: SAXParser parser = factory.newSAXParser();
133:
134: XMLReader xmlReader = parser.getXMLReader();
135: xmlReader.setEntityResolver(new EntityResolver() {
136: public InputSource resolveEntity(String publicId,
137: String systemId) {
138: return new InputSource(getClass().getClassLoader()
139: .getResourceAsStream("properties.dtd"));
140: }
141: });
142: xmlReader.setContentHandler(new XMLPropertiesHandler());
143: xmlReader.parse(new InputSource(in));
144: } catch (Exception e) {
145: throw new ConfigurationException(
146: "Unable to parse the configuration file", e);
147: }
148:
149: // todo: support included properties ?
150: }
151:
152: public void save(Writer out) throws ConfigurationException {
153: PrintWriter writer = new PrintWriter(out);
154:
155: String encoding = getEncoding() != null ? getEncoding()
156: : DEFAULT_ENCODING;
157: writer.println("<?xml version=\"1.0\" encoding=\"" + encoding
158: + "\"?>");
159: writer
160: .println("<!DOCTYPE properties SYSTEM \"http://java.sun.com/dtd/properties.dtd\">");
161: writer.println("<properties>");
162:
163: if (getHeader() != null) {
164: writer.println(" <comment>"
165: + StringEscapeUtils.escapeXml(getHeader())
166: + "</comment>");
167: }
168:
169: Iterator keys = getKeys();
170: while (keys.hasNext()) {
171: String key = (String) keys.next();
172: Object value = getProperty(key);
173:
174: if (value instanceof List) {
175: writeProperty(writer, key, (List) value);
176: } else {
177: writeProperty(writer, key, value);
178: }
179: }
180:
181: writer.println("</properties>");
182: writer.flush();
183: }
184:
185: /**
186: * Write a property.
187: *
188: * @param out the output stream
189: * @param key the key of the property
190: * @param value the value of the property
191: */
192: private void writeProperty(PrintWriter out, String key, Object value) {
193: // escape the key
194: String k = StringEscapeUtils.escapeXml(key);
195:
196: if (value != null) {
197: // escape the value
198: String v = StringEscapeUtils.escapeXml(String
199: .valueOf(value));
200: v = StringUtils.replace(v, String
201: .valueOf(getListDelimiter()), "\\"
202: + getListDelimiter());
203:
204: out.println(" <entry key=\"" + k + "\">" + v + "</entry>");
205: } else {
206: out.println(" <entry key=\"" + k + "\"/>");
207: }
208: }
209:
210: /**
211: * Write a list property.
212: *
213: * @param out the output stream
214: * @param key the key of the property
215: * @param values a list with all property values
216: */
217: private void writeProperty(PrintWriter out, String key, List values) {
218: for (int i = 0; i < values.size(); i++) {
219: writeProperty(out, key, values.get(i));
220: }
221: }
222:
223: /**
224: * SAX Handler to parse a XML properties file.
225: *
226: * @author Alistair Young
227: * @since 1.2
228: */
229: private class XMLPropertiesHandler extends DefaultHandler {
230: /** The key of the current entry being parsed. */
231: private String key;
232:
233: /** The value of the current entry being parsed. */
234: private StringBuffer value = new StringBuffer();
235:
236: /** Indicates that a comment is being parsed. */
237: private boolean inCommentElement;
238:
239: /** Indicates that an entry is being parsed. */
240: private boolean inEntryElement;
241:
242: public void startElement(String uri, String localName,
243: String qName, Attributes attrs) {
244: if ("comment".equals(qName)) {
245: inCommentElement = true;
246: }
247:
248: if ("entry".equals(qName)) {
249: key = attrs.getValue("key");
250: inEntryElement = true;
251: }
252: }
253:
254: public void endElement(String uri, String localName,
255: String qName) {
256: if (inCommentElement) {
257: // We've just finished a <comment> element so set the header
258: setHeader(value.toString());
259: inCommentElement = false;
260: }
261:
262: if (inEntryElement) {
263: // We've just finished an <entry> element, so add the key/value pair
264: addProperty(key, value.toString());
265: inEntryElement = false;
266: }
267:
268: // Clear the element value buffer
269: value = new StringBuffer();
270: }
271:
272: public void characters(char[] chars, int start, int length) {
273: /**
274: * We're currently processing an element. All character data from now until
275: * the next endElement() call will be the data for this element.
276: */
277: value.append(chars, start, length);
278: }
279: }
280: }
|