/* * Distributed as part of mchange-commons-java 0.2.11 * * Copyright (C) 2015 Machinery For Change, Inc. * * Author: Steve Waldman <swaldman@mchange.com> * * This library is free software; you can redistribute it and/or modify * it under the terms of EITHER: * * 1) The GNU Lesser General Public License (LGPL), version 2.1, as * published by the Free Software Foundation * * OR * * 2) The Eclipse Public License (EPL), version 1.0 * * You may choose which license to accept if you wish to redistribute * or modify this work. You may offer derivatives of this work * under the license you have chosen, or you may provide the same * choice of license which you have been offered here. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * You should have received copies of both LGPL v2.1 and EPL v1.0 * along with this software; see the files LICENSE-EPL and LICENSE-LGPL. * If not, the text of these licenses are currently available at * * LGPL v2.1: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html * EPL v1.0: http://www.eclipse.org/org/documents/epl-v10.php * */ package com.mchange.v2.util; import java.io.*; import java.util.*; import javax.xml.parsers.*; import javax.xml.transform.*; import javax.xml.transform.dom.*; import javax.xml.transform.stream.*; import org.w3c.dom.*; import org.xml.sax.*; //Example Document: // // <?xml version="1.0" encoding="UTF-8"?> // // <!DOCTYPE xml-properties SYSTEM "http://www.mchange.com/dtd/xml-properties.dtd"> // // <xml-properties> // <property name="key1">This is the first value.</property> // <property name="key2" trim="false">Spaced Label: </property> // <property name="key3" trim="true"><![CDATA[ // <h1>Whatever.</h1> // ]]></property> // </xml-properties> public class XmlProperties extends Properties { final static String DTD_SYSTEM_ID = "http://www.mchange.com/dtd/xml-properties.dtd"; final static String DTD_RSRC_PATH = "dtd/xml-properties.dtd"; //MT: members not thread-safe, but access protected by this' lock DocumentBuilder docBuilder; Transformer identityTransformer; public XmlProperties() throws ParserConfigurationException, TransformerConfigurationException { EntityResolver er = new EntityResolver() { public InputSource resolveEntity (String publicId, String systemId) { if (DTD_SYSTEM_ID.equals( systemId )) { InputStream is = XmlProperties.class.getResourceAsStream(DTD_RSRC_PATH); return new InputSource(is); } else return null; } }; ErrorHandler eh = new ErrorHandler() { public void warning(SAXParseException e) throws SAXException { System.err.println("[Warning] " + e.toString()); } public void error(SAXParseException e) throws SAXException { System.err.println("[Error] " + e.toString()); } public void fatalError(SAXParseException e) throws SAXException { System.err.println("[Fatal Error] " + e.toString()); } }; DocumentBuilderFactory dbFact = DocumentBuilderFactory.newInstance(); dbFact.setValidating( true ); dbFact.setCoalescing( true ); dbFact.setIgnoringComments( true ); this.docBuilder = dbFact.newDocumentBuilder(); this.docBuilder.setEntityResolver( er ); this.docBuilder.setErrorHandler( eh ); TransformerFactory tFact = TransformerFactory.newInstance(); this.identityTransformer = tFact.newTransformer(); this.identityTransformer.setOutputProperty("indent", "yes"); this.identityTransformer.setOutputProperty("doctype-system", DTD_SYSTEM_ID); } public synchronized void loadXml(InputStream is) throws IOException, SAXException { Document doc = docBuilder.parse( is ); NodeList nl = doc.getElementsByTagName("property"); for( int i = 0, len = nl.getLength(); i < len; ++i) extractProperty( nl.item( i ) ); } /* * This method presumes validation by our DTD and a coalescing, * comment-ignoring parser! */ private void extractProperty( Node propNode ) { Element propElem = (Element) propNode; String key = propElem.getAttribute( "name" ); boolean trim = Boolean.valueOf( propElem.getAttribute("trim") ).booleanValue(); NodeList nl = propElem.getChildNodes(); int len = nl.getLength(); assert (len >= 0 && len <= 1) : "Bad number of children of property element: " + len; String val = ( len == 0 ? "" : ((Text) nl.item(0)).getNodeValue() ); if (trim) val = val.trim(); this.put( key, val ); } public synchronized void saveXml(OutputStream os) throws IOException, TransformerException { storeXml( os, null ); } public synchronized void storeXml(OutputStream os, String header) throws IOException, TransformerException { Document doc = docBuilder.newDocument(); if (header != null) { Comment comment = doc.createComment( header ); doc.appendChild( comment ); } Element xmlPropsElem = doc.createElement("xml-properties"); for( Iterator ii = this.keySet().iterator(); ii.hasNext(); ) { Element propsElem = doc.createElement("property"); String key = (String) ii.next(); String val = (String) this.get(key); propsElem.setAttribute( "name", key ); Text valNode = doc.createTextNode(val); propsElem.appendChild( valNode ); xmlPropsElem.appendChild( propsElem ); } doc.appendChild( xmlPropsElem ); identityTransformer.transform( new DOMSource( doc ), new StreamResult( os ) ); } public static void main( String[] argv ) { InputStream is = null; OutputStream os = null; try { is = new BufferedInputStream( new FileInputStream( argv[0] ) ); os = new BufferedOutputStream( new FileOutputStream( argv[1] ) ); XmlProperties xmlProps = new XmlProperties(); xmlProps.loadXml( is ); xmlProps.list( System.out ); xmlProps.storeXml( os, "This is the resaved test document." ); os.flush(); } catch ( Exception e ) { e.printStackTrace(); } finally { try { if (is != null) is.close(); } catch (Exception e) { e.printStackTrace(); } try { if (os != null) os.close(); } catch (Exception e) { e.printStackTrace(); } } } }