package edu.cmu.sphinx.util.props; import org.xml.sax.Attributes; import org.xml.sax.Locator; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; import org.xml.sax.helpers.DefaultHandler; import java.io.File; import java.io.IOException; import java.net.URISyntaxException; import java.net.URL; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.logging.Logger; /** A SAX XML Handler implementation that builds up the map of raw property data objects */ public class ConfigHandler extends DefaultHandler { protected RawPropertyData rpd; protected Locator locator; protected List<String> itemList; protected String itemListName; protected StringBuilder curItem; protected final Map<String, RawPropertyData> rpdMap; protected final Map<String, String> globalProperties; private boolean replaceDuplicates; private final URL baseURL; public ConfigHandler(Map<String, RawPropertyData> rpdMap, Map<String, String> globalProperties, boolean replaceDuplicates, URL baseURL) { this.rpdMap = rpdMap; this.globalProperties = globalProperties; this.replaceDuplicates = replaceDuplicates; this.baseURL = baseURL; } public ConfigHandler(Map<String, RawPropertyData> rpdMap, Map<String, String> globalProperties) { this(rpdMap, globalProperties, false, null); } @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { if (qName.equals("config")) { // test if this configuration extends another one String extendedConfigName = attributes.getValue("extends"); if (extendedConfigName != null) { mergeConfigs(extendedConfigName, true); replaceDuplicates = true; } } else if (qName.equals("include")) { String includeFileName = attributes.getValue("file"); mergeConfigs(includeFileName, false); } else if (qName.equals("extendwith")) { String includeFileName = attributes.getValue("file"); mergeConfigs(includeFileName, true); } else if (qName.equals("component")) { String curComponent = attributes.getValue("name"); String curType = attributes.getValue("type"); if (rpdMap.get(curComponent) != null && !replaceDuplicates) { throw new SAXParseException("duplicate definition for " + curComponent, locator); } rpd = new RawPropertyData(curComponent, curType); } else if (qName.equals("property")) { String name = attributes.getValue("name"); String value = attributes.getValue("value"); if (attributes.getLength() != 2 || name == null || value == null) { throw new SAXParseException("property element must only have 'name' and 'value' attributes", locator); } if (rpd == null) { // we are not in a component so add this to the global // set of symbols // String symbolName = "${" + name + "}"; // why should we warp the global props here globalProperties.put(name, value); } else if (rpd.contains(name) && !replaceDuplicates) { throw new SAXParseException("Duplicate property: " + name, locator); } else { rpd.add(name, value); } } else if (qName.equals("propertylist")) { itemListName = attributes.getValue("name"); if (attributes.getLength() != 1 || itemListName == null) { throw new SAXParseException("list element must only have the 'name' attribute", locator); } itemList = new ArrayList<String>(); } else if (qName.equals("item")) { if (attributes.getLength() != 0) { throw new SAXParseException("unknown 'item' attribute", locator); } curItem = new StringBuilder(); } else { throw new SAXParseException("Unknown element '" + qName + '\'', locator); } } @Override public void characters(char ch[], int start, int length) throws SAXParseException { if (curItem != null) { curItem.append(ch, start, length); } } @Override public void endElement(String uri, String localName, String qName) throws SAXParseException { if (qName.equals("component")) { rpdMap.put(rpd.getName(), rpd); rpd = null; } else if (qName.equals("property")) { // nothing to do } else if (qName.equals("propertylist")) { if (rpd.contains(itemListName)) { throw new SAXParseException("Duplicate property: " + itemListName, locator); } else { rpd.add(itemListName, itemList); itemList = null; } } else if (qName.equals("item")) { itemList.add(curItem.toString().trim()); curItem = null; } } @Override public void setDocumentLocator(Locator locator) { this.locator = locator; } private void mergeConfigs(String configFileName, boolean replaceDuplicates) { try { File parent = new File(baseURL.toURI().getPath()).getParentFile(); URL fileURL = new File(parent.getPath() + File.separatorChar + configFileName).toURI().toURL(); Logger logger = Logger.getLogger(ConfigHandler.class.getSimpleName()); logger.fine((replaceDuplicates ? "extending" : "including") + " config:" + fileURL.toURI()); SaxLoader saxLoader = new SaxLoader(fileURL, globalProperties, rpdMap, replaceDuplicates); saxLoader.load(); } catch (IOException e) { throw new RuntimeException("Error while processing <include file=\"" + configFileName + "\">: " + e, e); } catch (URISyntaxException e) { e.printStackTrace(); } } }