/** * Copyright (C) 2001-2017 by RapidMiner and the contributors * * Complete list of developers available at our web site: * * http://rapidminer.com * * This program is free software: you can redistribute it and/or modify it under the terms of the * GNU Affero General Public License as published by the Free Software Foundation, either version 3 * of the License, or (at your option) any later version. * * This program 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. See the GNU * Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License along with this program. * If not, see http://www.gnu.org/licenses/. */ package com.rapidminer.gui.properties; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.TreeMap; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; import com.rapidminer.gui.properties.SettingsItem.Type; /** * XML SAX parser handler for the hierarchy of settings items. The parsing process is started by * {@link SettingsXmlHandler#parse(URI)}. * * @author Adrian Wilke */ public class SettingsXmlHandler extends DefaultHandler { /** XML file with settings structure */ public static final String SETTINGS_XML_FILE = "settings.xml"; /** Prefix of subgroups in settings XML */ public final static String SUBGROUP_PREFIX = "rapidminer.preferences.subgroup."; /** Maximum number of nested group tags */ private final static int MAX_GROUP_LEVEL = 2; /* Used XML elements */ private final static String TAG_ROOT = "settings"; private final static String TAG_GROUP = "group"; private final static String TAG_PROPERTY = "property"; private final static String ATTRIBUTE_KEY = "key"; /* Parsing result: Maps ID to object */ private Map<String, SettingsItem> settingsItems = new LinkedHashMap<>(); /* Parser state */ private List<SettingsItem> itemStack = new LinkedList<>(); /** * Parses Settings XML file. * * @param xmlFileUri * The URI of the related XML settings file. * * @return Map containing item IDs which identify the related {@link SettingsItem}s. * * @throws ParserConfigurationException * @throws SAXException * @throws IOException * @throws URISyntaxException */ public Map<String, SettingsItem> parse(URI xmlFileUri) throws ParserConfigurationException, SAXException, IOException, URISyntaxException { SAXParser saxParser = SAXParserFactory.newInstance().newSAXParser(); saxParser.parse(xmlFileUri.toString(), this); return settingsItems; } /** XML Parser: Opening XML tag. */ @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { Map<String, String> attributesMap = getAttributesMap(attributes); if (qName.equals(TAG_ROOT)) { if (!itemStack.isEmpty()) { throw new SAXException("Invalid use of element " + TAG_ROOT); } } else if (qName.equals(TAG_GROUP) && itemStack.isEmpty()) { String propertyKey = getAttribute(attributesMap, ATTRIBUTE_KEY, true); itemStack.add(new SettingsItem(propertyKey, getLastStackItem(), Type.GROUP)); } else if (qName.equals(TAG_GROUP) && !itemStack.isEmpty() && itemStack.size() < MAX_GROUP_LEVEL) { if (!getLastStackItem().getType().equals(SettingsItem.Type.GROUP)) { throw new SAXException("Invalid use of element " + TAG_GROUP + ". Parent element is not " + TAG_GROUP); } String propertyKey = getAttribute(attributesMap, ATTRIBUTE_KEY, true); if (!propertyKey.startsWith(SUBGROUP_PREFIX)) { throw new SAXException("Invalid value for attribute '" + ATTRIBUTE_KEY + "' in element '" + TAG_GROUP + "': " + propertyKey); } itemStack.add(new SettingsItem(propertyKey, getLastStackItem(), Type.SUB_GROUP)); } else if (qName.equals(TAG_GROUP)) { String propertyKey = getAttribute(attributesMap, ATTRIBUTE_KEY, true); throw new SAXException("Invalid use of element '" + TAG_GROUP + "' (key: " + propertyKey + "')"); } else if (qName.equals(TAG_PROPERTY)) { if (itemStack.isEmpty()) { throw new SAXException("Invalid use of element " + TAG_PROPERTY); } else if (!getLastStackItem().getType().equals(SettingsItem.Type.GROUP) && !getLastStackItem().getType().equals(SettingsItem.Type.SUB_GROUP)) { throw new SAXException("Invalid use of element " + TAG_PROPERTY + ". Parent element is not " + TAG_GROUP); } String propertyKey = getAttribute(attributesMap, ATTRIBUTE_KEY, true); itemStack.add(new SettingsItem(propertyKey, getLastStackItem(), Type.PARAMETER)); } else { throw new SAXException("Unknown tag or usage: " + qName); } } /** XML Parser: Closing XML tag. */ @Override public void endElement(String uri, String localName, String qName) throws SAXException { if (qName.equals(TAG_ROOT)) { // Just maintains a valid XML structure } else if (qName.equals(TAG_GROUP) || qName.equals(TAG_PROPERTY)) { // Update stack SettingsItem item = itemStack.remove(itemStack.size() - 1); // Add to results settingsItems.put(item.getKey(), item); } else { throw new SAXException("Unknown tag or usage: " + localName); } } /** * Gets the last parsed settings item. * * @return Parent settings item, if exists. Or null, if no parent settings item exists. */ private SettingsItem getLastStackItem() { if (itemStack.isEmpty()) { return null; } else { return itemStack.get(itemStack.size() - 1); } } /** * Gets the attribute of the specified key. * * @return Attribute, if key is in map. Or null, if key is not in map. * @throws SAXException * if key not in map and isNecessary is set */ private String getAttribute(Map<String, String> attributes, String key, boolean isNecessary) throws SAXException { if (attributes.containsKey(key)) { return attributes.get(key); } else if (isNecessary) { throw new SAXException("Attribute " + key + " not found."); } else { return null; } } /** * Returns a map representation of the specified attributes. */ private Map<String, String> getAttributesMap(Attributes attributes) { Map<String, String> map = new TreeMap<>(); for (int i = 0; i < attributes.getLength(); i++) { map.put(attributes.getLocalName(i), attributes.getValue(i)); } return map; } }