| /* |
| * Copyright 2004-2005 The Apache Software Foundation. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License") |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package org.apache.commons.configuration; |
| |
| import java.io.File; |
| import java.io.PrintWriter; |
| import java.io.Reader; |
| import java.io.Writer; |
| import java.net.URL; |
| import java.util.Iterator; |
| import java.util.List; |
| import javax.xml.parsers.SAXParser; |
| import javax.xml.parsers.SAXParserFactory; |
| |
| import org.apache.commons.lang.StringEscapeUtils; |
| import org.apache.commons.lang.StringUtils; |
| |
| import org.xml.sax.Attributes; |
| import org.xml.sax.EntityResolver; |
| import org.xml.sax.InputSource; |
| import org.xml.sax.XMLReader; |
| import org.xml.sax.helpers.DefaultHandler; |
| |
| /** |
| * This configuration implements the XML properties format introduced in Java |
| * 5.0, see http://java.sun.com/j2se/1.5.0/docs/api/java/util/Properties.html. |
| * An XML properties file looks like this: |
| * |
| * <pre> |
| * <?xml version="1.0"?> |
| * <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"> |
| * <properties> |
| * <comment>Description of the property list</comment> |
| * <entry key="key1">value1</entry> |
| * <entry key="key2">value2</entry> |
| * <entry key="key3">value3</entry> |
| * </properties> |
| * </pre> |
| * |
| * The Java 5.0 runtime is not required to use this class. The default encoding |
| * for this configuration format is UTF-8. Note that unlike |
| * <code>PropertiesConfiguration</code>, <code>XMLPropertiesConfiguration</code> |
| * does not support includes. |
| * |
| * @author Emmanuel Bourg |
| * @author Alistair Young |
| * @version $Revision$, $Date$ |
| * @since 1.1 |
| */ |
| public class XMLPropertiesConfiguration extends PropertiesConfiguration |
| { |
| /** |
| * The default encoding (UTF-8 as specified by http://java.sun.com/j2se/1.5.0/docs/api/java/util/Properties.html) |
| */ |
| private static final String DEFAULT_ENCODING = "UTF-8"; |
| |
| // initialization block to set the encoding before loading the file in the constructors |
| { |
| setEncoding(DEFAULT_ENCODING); |
| } |
| |
| /** |
| * Creates an empty XMLPropertyConfiguration object which can be |
| * used to synthesize a new Properties file by adding values and |
| * then saving(). An object constructed by this C'tor can not be |
| * tickled into loading included files because it cannot supply a |
| * base for relative includes. |
| */ |
| public XMLPropertiesConfiguration() |
| { |
| super(); |
| } |
| |
| /** |
| * Creates and loads the xml properties from the specified file. |
| * The specified file can contain "include" properties which then |
| * are loaded and merged into the properties. |
| * |
| * @param fileName The name of the properties file to load. |
| * @throws ConfigurationException Error while loading the properties file |
| */ |
| public XMLPropertiesConfiguration(String fileName) throws ConfigurationException |
| { |
| super(fileName); |
| } |
| |
| /** |
| * Creates and loads the xml properties from the specified file. |
| * The specified file can contain "include" properties which then |
| * are loaded and merged into the properties. |
| * |
| * @param file The properties file to load. |
| * @throws ConfigurationException Error while loading the properties file |
| */ |
| public XMLPropertiesConfiguration(File file) throws ConfigurationException |
| { |
| super(file); |
| } |
| |
| /** |
| * Creates and loads the xml properties from the specified URL. |
| * The specified file can contain "include" properties which then |
| * are loaded and merged into the properties. |
| * |
| * @param url The location of the properties file to load. |
| * @throws ConfigurationException Error while loading the properties file |
| */ |
| public XMLPropertiesConfiguration(URL url) throws ConfigurationException |
| { |
| super(url); |
| } |
| |
| public void load(Reader in) throws ConfigurationException |
| { |
| SAXParserFactory factory = SAXParserFactory.newInstance(); |
| factory.setNamespaceAware(false); |
| factory.setValidating(true); |
| |
| try |
| { |
| SAXParser parser = factory.newSAXParser(); |
| |
| XMLReader xmlReader = parser.getXMLReader(); |
| xmlReader.setEntityResolver(new EntityResolver() |
| { |
| public InputSource resolveEntity(String publicId, String systemId) |
| { |
| return new InputSource(getClass().getClassLoader().getResourceAsStream("properties.dtd")); |
| } |
| }); |
| xmlReader.setContentHandler(new XMLPropertiesHandler()); |
| xmlReader.parse(new InputSource(in)); |
| } |
| catch (Exception e) |
| { |
| throw new ConfigurationException("Unable to parse the configuration file", e); |
| } |
| |
| // todo: support included properties ? |
| } |
| |
| public void save(Writer out) throws ConfigurationException |
| { |
| PrintWriter writer = new PrintWriter(out); |
| |
| String encoding = getEncoding() != null ? getEncoding() : DEFAULT_ENCODING; |
| writer.println("<?xml version=\"1.0\" encoding=\"" + encoding + "\"?>"); |
| writer.println("<!DOCTYPE properties SYSTEM \"http://java.sun.com/dtd/properties.dtd\">"); |
| writer.println("<properties>"); |
| |
| if (getHeader() != null) |
| { |
| writer.println(" <comment>" + StringEscapeUtils.escapeXml(getHeader()) + "</comment>"); |
| } |
| |
| Iterator keys = getKeys(); |
| while (keys.hasNext()) |
| { |
| String key = (String) keys.next(); |
| Object value = getProperty(key); |
| |
| if (value instanceof List) |
| { |
| writeProperty(writer, key, (List) value); |
| } |
| else |
| { |
| writeProperty(writer, key, value); |
| } |
| } |
| |
| writer.println("</properties>"); |
| writer.flush(); |
| } |
| |
| /** |
| * Write a property. |
| * |
| * @param out the output stream |
| * @param key the key of the property |
| * @param value the value of the property |
| */ |
| private void writeProperty(PrintWriter out, String key, Object value) |
| { |
| // escape the key |
| String k = StringEscapeUtils.escapeXml(key); |
| |
| if (value != null) |
| { |
| // escape the value |
| String v = StringEscapeUtils.escapeXml(String.valueOf(value)); |
| v = StringUtils.replace(v, String.valueOf(getDelimiter()), "\\" + getDelimiter()); |
| |
| out.println(" <entry key=\"" + k + "\">" + v + "</entry>"); |
| } |
| else |
| { |
| out.println(" <entry key=\"" + k + "\"/>"); |
| } |
| } |
| |
| /** |
| * Write a list property. |
| * |
| * @param out the output stream |
| * @param key the key of the property |
| * @param values a list with all property values |
| */ |
| private void writeProperty(PrintWriter out, String key, List values) |
| { |
| for (int i = 0; i < values.size(); i++) |
| { |
| writeProperty(out, key, values.get(i)); |
| } |
| } |
| |
| /** |
| * SAX Handler to parse a XML properties file. |
| * |
| * @author Alistair Young |
| * @since 1.2 |
| */ |
| private class XMLPropertiesHandler extends DefaultHandler |
| { |
| /** The key of the current entry being parsed. */ |
| private String key; |
| |
| /** The value of the current entry being parsed. */ |
| private StringBuffer value = new StringBuffer(); |
| |
| /** Indicates that a comment is being parsed. */ |
| private boolean inCommentElement; |
| |
| /** Indicates that an entry is being parsed. */ |
| private boolean inEntryElement; |
| |
| public void startElement(String uri, String localName, String qName, Attributes attrs) |
| { |
| if ("comment".equals(qName)) |
| { |
| inCommentElement = true; |
| } |
| |
| if ("entry".equals(qName)) |
| { |
| key = attrs.getValue("key"); |
| inEntryElement = true; |
| } |
| } |
| |
| public void endElement(String uri, String localName, String qName) |
| { |
| if (inCommentElement) |
| { |
| // We've just finished a <comment> element so set the header |
| setHeader(value.toString()); |
| inCommentElement = false; |
| } |
| |
| if (inEntryElement) |
| { |
| // We've just finished an <entry> element, so add the key/value pair |
| addProperty(key, value.toString()); |
| inEntryElement = false; |
| } |
| |
| // Clear the element value buffer |
| value = new StringBuffer(); |
| } |
| |
| public void characters(char[] chars, int start, int length) |
| { |
| /** |
| * We're currently processing an element. All character data from now until |
| * the next endElement() call will be the data for this element. |
| */ |
| value.append(chars, start, length); |
| } |
| } |
| } |