/******************************************************************************* * This file is part of logisim-evolution. * * logisim-evolution is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * logisim-evolution 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with logisim-evolution. If not, see <http://www.gnu.org/licenses/>. * * Original code by Carl Burch (http://www.cburch.com), 2011. * Subsequent modifications by : * + Haute École Spécialisée Bernoise * http://www.bfh.ch * + Haute École du paysage, d'ingénierie et d'architecture de Genève * http://hepia.hesge.ch/ * + Haute École d'Ingénierie et de Gestion du Canton de Vaud * http://www.heig-vd.ch/ * The project is currently maintained by : * + REDS Institute - HEIG-VD * Yverdon-les-Bains, Switzerland * http://reds.heig-vd.ch *******************************************************************************/ package com.cburch.logisim.file; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.UUID; import javax.swing.JOptionPane; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.xml.sax.SAXException; import com.cburch.draw.model.AbstractCanvasObject; import com.cburch.logisim.LogisimVersion; import com.cburch.logisim.Main; import com.cburch.logisim.circuit.Circuit; import com.cburch.logisim.circuit.appear.AppearanceSvgReader; import com.cburch.logisim.comp.Component; import com.cburch.logisim.data.Attribute; import com.cburch.logisim.data.AttributeDefaultProvider; import com.cburch.logisim.data.AttributeSet; import com.cburch.logisim.data.Location; import com.cburch.logisim.instance.Instance; import com.cburch.logisim.proj.Project; import com.cburch.logisim.std.wiring.Pin; import com.cburch.logisim.tools.Library; import com.cburch.logisim.tools.Tool; import com.cburch.logisim.util.InputEventUtil; import com.cburch.logisim.util.StringUtil; class XmlReader { static class CircuitData { Element circuitElement; Circuit circuit; Map<Element, Component> knownComponents; List<AbstractCanvasObject> appearance; public CircuitData(Element circuitElement, Circuit circuit) { this.circuitElement = circuitElement; this.circuit = circuit; } } class ReadContext { LogisimFile file; LogisimVersion sourceVersion; HashMap<String, Library> libs = new HashMap<String, Library>(); private ArrayList<String> messages; ReadContext(LogisimFile file) { this.file = file; this.messages = new ArrayList<String>(); } void addError(String message, String context) { messages.add(message + " [" + context + "]"); } void addErrors(XmlReaderException exception, String context) { for (String msg : exception.getMessages()) { messages.add(msg + " [" + context + "]"); } } Library findLibrary(String lib_name) throws XmlReaderException { if (lib_name == null || lib_name.equals("")) { return file; } Library ret = libs.get(lib_name); if (ret == null) { throw new XmlReaderException(StringUtil.format( Strings.get("libMissingError"), lib_name)); } else { return ret; } } void initAttributeSet(Element parentElt, AttributeSet attrs, AttributeDefaultProvider defaults) throws XmlReaderException { ArrayList<String> messages = null; HashMap<String, String> attrsDefined = new HashMap<String, String>(); for (Element attrElt : XmlIterator.forChildElements(parentElt, "a")) { if (!attrElt.hasAttribute("name")) { if (messages == null) messages = new ArrayList<String>(); messages.add(Strings.get("attrNameMissingError")); } else { String attrName = attrElt.getAttribute("name"); String attrVal; if (attrElt.hasAttribute("val")) { attrVal = attrElt.getAttribute("val"); if (attrName.equals("filePath")) { /* De-relativize the path */ String dirPath = ""; if (srcFilePath != null) dirPath = srcFilePath .substring(0, srcFilePath .lastIndexOf(File.separator)); Path tmp = Paths.get(dirPath, attrVal); attrVal = tmp.toString(); } } else { attrVal = attrElt.getTextContent(); } attrsDefined.put(attrName, attrVal); } } if (attrs == null) return; LogisimVersion ver = sourceVersion; boolean setDefaults = defaults != null && !defaults.isAllDefaultValues(attrs, ver); // We need to process this in order, and we have to refetch the // attribute list each time because it may change as we iterate // (as it will for a splitter). for (int i = 0; true; i++) { List<Attribute<?>> attrList = attrs.getAttributes(); if (i >= attrList.size()) break; @SuppressWarnings("unchecked") Attribute<Object> attr = (Attribute<Object>) attrList.get(i); String attrName = attr.getName(); String attrVal = attrsDefined.get(attrName); if (attrVal == null) { if (setDefaults) { Object val = defaults.getDefaultAttributeValue(attr, ver); if (val != null) { attrs.setValue(attr, val); } } } else { try { Object val = attr.parse(attrVal); attrs.setValue(attr, val); } catch (NumberFormatException e) { if (messages == null) messages = new ArrayList<String>(); messages.add(StringUtil.format( Strings.get("attrValueInvalidError"), attrVal, attrName)); } } } if (messages != null) { throw new XmlReaderException(messages); } } private void initMouseMappings(Element elt) { MouseMappings map = file.getOptions().getMouseMappings(); for (Element sub_elt : XmlIterator.forChildElements(elt, "tool")) { Tool tool; try { tool = toTool(sub_elt); } catch (XmlReaderException e) { addErrors(e, "mapping"); continue; } String mods_str = sub_elt.getAttribute("map"); if (mods_str == null || mods_str.equals("")) { loader.showError(Strings.get("mappingMissingError")); continue; } int mods; try { mods = InputEventUtil.fromString(mods_str); } catch (NumberFormatException e) { loader.showError(StringUtil.format( Strings.get("mappingBadError"), mods_str)); continue; } tool = tool.cloneTool(); try { initAttributeSet(sub_elt, tool.getAttributeSet(), tool); } catch (XmlReaderException e) { addErrors(e, "mapping." + tool.getName()); } map.setToolFor(mods, tool); } } private void initToolbarData(Element elt) { ToolbarData toolbar = file.getOptions().getToolbarData(); for (Element sub_elt : XmlIterator.forChildElements(elt)) { if (sub_elt.getTagName().equals("sep")) { toolbar.addSeparator(); } else if (sub_elt.getTagName().equals("tool")) { Tool tool; try { tool = toTool(sub_elt); } catch (XmlReaderException e) { addErrors(e, "toolbar"); continue; } if (tool != null) { tool = tool.cloneTool(); try { initAttributeSet(sub_elt, tool.getAttributeSet(), tool); } catch (XmlReaderException e) { addErrors(e, "toolbar." + tool.getName()); } toolbar.addTool(tool); } } } } private void loadAppearance(Element appearElt, CircuitData circData, String context) { Map<Location, Instance> pins = new HashMap<Location, Instance>(); for (Component comp : circData.knownComponents.values()) { if (comp.getFactory() == Pin.FACTORY) { Instance instance = Instance.getInstanceFor(comp); pins.put(comp.getLocation(), instance); } } List<AbstractCanvasObject> shapes = new ArrayList<AbstractCanvasObject>(); for (Element sub : XmlIterator.forChildElements(appearElt)) { try { AbstractCanvasObject m = AppearanceSvgReader.createShape( sub, pins); if (m == null) { addError( Strings.get("fileAppearanceNotFound", sub.getTagName()), context + "." + sub.getTagName()); } else { shapes.add(m); } } catch (RuntimeException e) { addError(Strings.get("fileAppearanceError", sub.getTagName()), context + "." + sub.getTagName()); } } if (!shapes.isEmpty()) { if (circData.appearance == null) { circData.appearance = shapes; } else { circData.appearance.addAll(shapes); } } } private Map<Element, Component> loadKnownComponents(Element elt) { Map<Element, Component> known = new HashMap<Element, Component>(); for (Element sub : XmlIterator.forChildElements(elt, "comp")) { try { Component comp = XmlCircuitReader.getComponent(sub, this); if (comp != null) known.put(sub, comp); } catch (XmlReaderException e) { } } return known; } private Library toLibrary(Element elt) { if (!elt.hasAttribute("name")) { loader.showError(Strings.get("libNameMissingError")); return null; } if (!elt.hasAttribute("desc")) { loader.showError(Strings.get("libDescMissingError")); return null; } String name = elt.getAttribute("name"); String desc = elt.getAttribute("desc"); Library ret = loader.loadLibrary(desc); if (ret == null) return null; libs.put(name, ret); for (Element sub_elt : XmlIterator.forChildElements(elt, "tool")) { if (!sub_elt.hasAttribute("name")) { loader.showError(Strings.get("toolNameMissingError")); } else { String tool_str = sub_elt.getAttribute("name"); Tool tool = ret.getTool(tool_str); if (tool != null) { try { initAttributeSet(sub_elt, tool.getAttributeSet(), tool); } catch (XmlReaderException e) { addErrors(e, "lib." + name + "." + tool_str); } } } } return ret; } private void toLogisimFile(Element elt,Project proj) { // determine the version producing this file String versionString = elt.getAttribute("source"); if (versionString.equals("")) { sourceVersion = Main.VERSION; } else { sourceVersion = LogisimVersion.parse(versionString); } // If we are opening a pre-logisim-evolution file, there might be // some components // (such as the RAM or the counters), that have changed their shape // and other details. // We have therefore to warn the user that things might be a little // strange in their // circuits... if (sourceVersion.compareTo(LogisimVersion.get(2, 7, 2)) < 0) { JOptionPane .showMessageDialog( null, "You are opening a file created with original Logisim code.\n" + "You might encounter some problems in the execution, since some components evolved since then.\n" + "Moreover, labels will be converted to match VHDL limitations for variable names.", "Old file format -- compatibility mode", JOptionPane.WARNING_MESSAGE); } if (versionString.contains("t") && !Main.VERSION.hasTracker()) { JOptionPane .showMessageDialog( null, "The file you have opened contains tracked components.\nYou might encounter some problems in the execution.", "No tracking system available", JOptionPane.WARNING_MESSAGE); } // first, load the sublibraries for (Element o : XmlIterator.forChildElements(elt, "lib")) { Library lib = toLibrary(o); if (lib != null) file.addLibrary(lib); } // second, create the circuits - empty for now List<CircuitData> circuitsData = new ArrayList<CircuitData>(); for (Element circElt : XmlIterator.forChildElements(elt, "circuit")) { String name = circElt.getAttribute("name"); if (name == null || name.equals("")) { addError(Strings.get("circNameMissingError"), "C??"); } CircuitData circData = new CircuitData(circElt, new Circuit( name, file,proj)); file.addCircuit(circData.circuit); circData.knownComponents = loadKnownComponents(circElt); for (Element appearElt : XmlIterator.forChildElements(circElt, "appear")) { loadAppearance(appearElt, circData, name + ".appear"); } circuitsData.add(circData); } // third, process the other child elements for (Element sub_elt : XmlIterator.forChildElements(elt)) { String name = sub_elt.getTagName(); switch (name) { case "circuit": case "lib": // Nothing to do: Done earlier. break; case "options": try { initAttributeSet(sub_elt, file.getOptions() .getAttributeSet(), null); } catch (XmlReaderException e) { addErrors(e, "options"); } break; case "mappings": initMouseMappings(sub_elt); break; case "toolbar": initToolbarData(sub_elt); break; case "main": String main = sub_elt.getAttribute("name"); Circuit circ = file.getCircuit(main); if (circ != null) { file.setMainCircuit(circ); } break; case "message": file.addMessage(sub_elt.getAttribute("value")); break; default: throw new IllegalArgumentException( "Invalid node in logisim file: " + name); } } // fourth, execute a transaction that initializes all the circuits XmlCircuitReader builder; builder = new XmlCircuitReader(this, circuitsData); builder.execute(); } Tool toTool(Element elt) throws XmlReaderException { Library lib = findLibrary(elt.getAttribute("lib")); String name = elt.getAttribute("name"); if (name == null || name.equals("")) { throw new XmlReaderException(Strings.get("toolNameMissing")); } Tool tool = lib.getTool(name); if (tool == null) { throw new XmlReaderException(Strings.get("toolNotFound")); } return tool; } } /** * Change label names in an XML tree according to a list of suggested labels * * @param root * root element of the XML tree * @param nodeType * type of nodes to consider * @param attrType * type of attributes to consider * @param validLabels * label set of correct label names */ public static void applyValidLabels(Element root, String nodeType, String attrType, Map<String, String> validLabels) throws IllegalArgumentException { assert (root != null); assert (nodeType != null); assert (attrType != null); assert (nodeType.length() > 0); assert (attrType.length() > 0); assert (validLabels != null); switch (nodeType) { case "circuit": replaceCircuitNodes(root, attrType, validLabels); break; case "comp": replaceCompNodes(root, validLabels); break; default: throw new IllegalArgumentException("Invalid node type requested: " + nodeType); } } /** * Sets to the empty string any label attribute in tool nodes derived from * elt. * * @param root * root node */ private static void cleanupToolsLabel(Element root) { assert (root != null); // Iterate on tools for (Element toolElt : XmlIterator.forChildElements(root, "tool")) { // Iterate on attribute nodes for (Element attrElt : XmlIterator.forChildElements(toolElt, "a")) { // Each attribute node should have a name field if (attrElt.hasAttribute("name")) { String aName = attrElt.getAttribute("name"); if (aName.equals("label")) { // Found a label node in a tool, clean it up! attrElt.setAttribute("val", ""); } } } } } public static Element ensureLogisimCompatibility(Element elt) { Map<String, String> validLabels; validLabels = findValidLabels(elt, "circuit", "name"); applyValidLabels(elt, "circuit", "name", validLabels); validLabels = findValidLabels(elt, "circuit", "label"); applyValidLabels(elt, "circuit", "label", validLabels); validLabels = findValidLabels(elt, "comp", "label"); applyValidLabels(elt, "comp", "label", validLabels); // In old, buggy Logisim versions, labels where incorrectly // stored also in toolbar and lib components. If this is the // case, clean them up. fixInvalidToolbarLib(elt); return (elt); } private static void findLibraryUses(ArrayList<Element> dest, String label, Iterable<Element> candidates) { for (Element elt : candidates) { String lib = elt.getAttribute("lib"); if (lib.equals(label)) { dest.add(elt); } } } /** * Check an XML tree for VHDL-incompatible labels, then propose a list of * valid ones. Here valid means: [a-zA-Z][a-zA-Z0-9_]* This applies, in our * context, to circuit's names and labels (and their corresponding * component's names, of course), and to comp's labels. * * @param root * root element of the XML tree * @param nodeType * type of nodes to consider * @param attrType * type of attributes to consider * @return map containing the original attribute values as keys, and the * corresponding valid attribute values as the values */ public static Map<String, String> findValidLabels(Element root, String nodeType, String attrType) { assert (root != null); assert (nodeType != null); assert (attrType != null); assert (nodeType.length() > 0); assert (attrType.length() > 0); Map<String, String> validLabels = new HashMap<String, String>(); List<String> initialLabels = getXMLLabels(root, nodeType, attrType); Iterator<String> iterator = initialLabels.iterator(); while (iterator.hasNext()) { String label = iterator.next(); if (!validLabels.containsKey(label)) { // Check if the name is invalid, in which case create // a valid version and put it in the map if (labelVHDLInvalid(label)) { String initialLabel = label; label = generateValidVHDLLabel(label); validLabels.put(initialLabel, label); } } } return validLabels; } /** * In some old version of Logisim, buggy Logisim versions, labels where * incorrectly stored also in toolbar and lib components. If this is the * case, clean them up.. * * @param root * root element of the XML tree */ private static void fixInvalidToolbarLib(Element root) { assert (root != null); // Iterate on toolbars -- though there should be only one! for (Element toolbarElt : XmlIterator.forChildElements(root, "toolbar")) { cleanupToolsLabel(toolbarElt); } // Iterate on libs for (Element libsElt : XmlIterator.forChildElements(root, "lib")) { cleanupToolsLabel(libsElt); } } /** * Given a label, generates a valid VHDL label by removing invalid * characters, putting a letter at the beginning, and putting a shortened (8 * characters) UUID at the end if the name has been altered. Whitespaces at * the beginning and at the end of the string are trimmed by default (if * this is the only change, then no suffix is appended). * * @param initialLabel * initial (possibly invalid) label * @return a valid VHDL label */ public static String generateValidVHDLLabel(String initialLabel) { return (generateValidVHDLLabel(initialLabel, UUID.randomUUID() .toString().substring(0, 8))); } /** * Given a label, generates a valid VHDL label by removing invalid * characters, putting a letter at the beginning, and putting the requested * suffix at the end if the name has been altered. Whitespaces at the * beginning and at the end of the string are trimmed by default (if this is * the only change, then no suffix is appended). * * @param initialLabel * initial (possibly invalid) label * @param suffix * string that has to be appended to a modified label * @return a valid VHDL label */ public static String generateValidVHDLLabel(String initialLabel, String suffix) { assert (initialLabel != null); // As a default, trim whitespaces at the beginning and at the end // of a label (no risks with that potentially, therefore avoid // to append the suffix if that was the only change) initialLabel = initialLabel.trim(); String label = initialLabel; if (label.isEmpty()) { logger.warn("Empty label is not a valid VHDL label"); label = "L_"; } // If the string has a ! or ~ symbol, then replace it with "NOT" label = label.replaceAll("[\\!~]", "NOT_"); // Force string to start with a letter if (!label.matches("^[A-Za-z].*$")) label = "L_" + label; // Force the rest to be either letters, or numbers, or underscores label = label.replaceAll("[^A-Za-z0-9_]", "_"); // Suppress multiple successive underscores and an underscore at the end label = label.replaceAll("_+", "_"); if (label.endsWith("_")) label = label.substring(0, label.length() - 1); if (!label.equals(initialLabel)) { // Concatenate a unique ID if the string has been altered label = label + "_" + suffix; // Replace the "-" characters in the UUID with underscores label = label.replaceAll("-", "_"); } return (label); } /** * Traverses an XML tree and gets a list of attribute values for the given * attribute and node types. * * @param root * root element of the XML tree * @param nodeType * type of nodes to consider * @param attrType * type of attributes to consider * @return list of names for the considered node/attribute pairs */ public static List<String> getXMLLabels(Element root, String nodeType, String attrType) throws IllegalArgumentException { assert (root != null); assert (nodeType != null); assert (attrType != null); assert (nodeType.length() > 0); assert (attrType.length() > 0); List<String> attrValuesList = new ArrayList<String>(); switch (nodeType) { case "circuit": inspectCircuitNodes(root, attrType, attrValuesList); break; case "comp": inspectCompNodes(root, attrValuesList); break; default: throw new IllegalArgumentException("Invalid node type requested: " + nodeType); } return attrValuesList; } /** * Check XML's circuit nodes, and return a list of values corresponding to * the desired attribute. * * @param root * XML's root * @param attrType * attribute type (either name or label) * @param attrValuesList * empty list that will contain the values found */ private static void inspectCircuitNodes(Element root, String attrType, List<String> attrValuesList) throws IllegalArgumentException { assert (root != null); assert (attrType != null); assert (attrValuesList != null); assert (attrValuesList.isEmpty()); // Circuits are top-level in the XML file switch (attrType) { case "name": for (Element circElt : XmlIterator .forChildElements(root, "circuit")) { // Circuit's name is directly available as an attribute String name = circElt.getAttribute("name"); attrValuesList.add(name); } break; case "label": for (Element circElt : XmlIterator .forChildElements(root, "circuit")) { // label is available through its a child node for (Element attrElt : XmlIterator.forChildElements(circElt, "a")) { if (attrElt.hasAttribute("name")) { String aName = attrElt.getAttribute("name"); if (aName.equals("label")) { String label = attrElt.getAttribute("val"); if (label.length() > 0) { attrValuesList.add(label); } } } } } break; default: throw new IllegalArgumentException( "Invalid attribute type requested: " + attrType + " for node type: circuit"); } } /** * Check XML's comp nodes, and return a list of values corresponding to the * desired attribute. The checked comp nodes are NOT those referring to * circuits -- we can see if this is the case by checking whether the lib * attribute is present or not. * * @param root * XML's root * @param attrValuesList * empty list that will contain the values found */ private static void inspectCompNodes(Element root, List<String> attrValuesList) { assert (root != null); assert (attrValuesList != null); assert (attrValuesList.isEmpty()); for (Element circElt : XmlIterator.forChildElements(root, "circuit")) { // In circuits, we have to look for components, then take // just those components that do have a lib attribute and look at // their // a child nodes for (Element compElt : XmlIterator .forChildElements(circElt, "comp")) { if (compElt.hasAttribute("lib")) { for (Element attrElt : XmlIterator.forChildElements( compElt, "a")) { if (attrElt.hasAttribute("name")) { String aName = attrElt.getAttribute("name"); if (aName.equals("label")) { String label = attrElt.getAttribute("val"); if (label.length() > 0) { attrValuesList.add(label); } } } } } } } } /** * Check if a given label could be a valid VHDL variable name * * @param label * candidate VHDL variable name * @return true if the label is NOT a valid name, false otherwise */ public static boolean labelVHDLInvalid(String label) { if (!label.matches("^[A-Za-z][A-Za-z0-9_]*") || label.endsWith("_") || label.matches(".*__.*")) return (true); return (false); } /** * Replace invalid labels in circuit nodes. * * @param root * XML's root * @param attrType * attribute type (either name or label) * @param validLabels * map containing valid label values */ private static void replaceCircuitNodes(Element root, String attrType, Map<String, String> validLabels) throws IllegalArgumentException { assert (root != null); assert (attrType != null); assert (validLabels != null); if (validLabels.isEmpty()) { // Particular case, all the labels were good! return; } // Circuits are top-level in the XML file switch (attrType) { case "name": // We have not only to replace the circuit names in each circuit, // but in the corresponding comps too! for (Element circElt : XmlIterator .forChildElements(root, "circuit")) { // Circuit's name is directly available as an attribute String name = circElt.getAttribute("name"); if (validLabels.containsKey(name)) { circElt.setAttribute("name", validLabels.get(name)); // Also, it is present as value for the "circuit" attribute for (Element attrElt : XmlIterator.forChildElements( circElt, "a")) { if (attrElt.hasAttribute("name")) { String aName = attrElt.getAttribute("name"); if (aName.equals("circuit")) { attrElt.setAttribute("val", validLabels.get(name)); } } } } // Now do the comp part for (Element compElt : XmlIterator.forChildElements(circElt, "comp")) { // Circuits are components without lib if (!compElt.hasAttribute("lib")) { if (compElt.hasAttribute("name")) { String cName = compElt.getAttribute("name"); if (validLabels.containsKey(cName)) { compElt.setAttribute("name", validLabels.get(cName)); } } } } } break; case "label": for (Element circElt : XmlIterator .forChildElements(root, "circuit")) { // label is available through its a child node for (Element attrElt : XmlIterator.forChildElements(circElt, "a")) { if (attrElt.hasAttribute("name")) { String aName = attrElt.getAttribute("name"); if (aName.equals("label")) { String label = attrElt.getAttribute("val"); if (validLabels.containsKey(label)) { attrElt.setAttribute("val", validLabels.get(label)); } } } } } break; default: throw new IllegalArgumentException( "Invalid attribute type requested: " + attrType + " for node type: circuit"); } } /** * Replace invalid labels in comp nodes. * * @param root * XML's root * @param validLabels * map containing valid label values */ private static void replaceCompNodes(Element root, Map<String, String> validLabels) { assert (root != null); assert (validLabels != null); if (validLabels.isEmpty()) { // Particular case, all the labels were good! return; } for (Element circElt : XmlIterator.forChildElements(root, "circuit")) { // In circuits, we have to look for components, then take // just those components that do have a lib attribute and look at // their // a child nodes for (Element compElt : XmlIterator .forChildElements(circElt, "comp")) { if (compElt.hasAttribute("lib")) { for (Element attrElt : XmlIterator.forChildElements( compElt, "a")) { if (attrElt.hasAttribute("name")) { String aName = attrElt.getAttribute("name"); if (aName.equals("label")) { String label = attrElt.getAttribute("val"); if (validLabels.containsKey(label)) { attrElt.setAttribute("val", validLabels.get(label)); } } } } } } } } public static final Logger logger = LoggerFactory .getLogger(XmlReader.class); private LibraryLoader loader; /** * Path of the source file -- it is used to make the paths of the components * stored in the file absolute, to prevent the system looking for them in * some strange directories. */ private String srcFilePath; XmlReader(Loader loader, File file) { this.loader = loader; if (file != null) this.srcFilePath = file.getAbsolutePath(); else this.srcFilePath = null; } private void addToLabelMap(HashMap<String, String> labelMap, String srcLabel, String dstLabel, String toolNames) { if (srcLabel != null && dstLabel != null) { for (String tool : toolNames.split(";")) { labelMap.put(srcLabel + ":" + tool, dstLabel); } } } private void considerRepairs(Document doc, Element root) { LogisimVersion version = LogisimVersion.parse(root .getAttribute("source")); if (version.compareTo(LogisimVersion.get(2, 3, 0)) < 0) { // This file was saved before an Edit tool existed. Most likely // we should replace the Select and Wiring tools in the toolbar // with the Edit tool instead. for (Element toolbar : XmlIterator .forChildElements(root, "toolbar")) { Element wiring = null; Element select = null; Element edit = null; for (Element elt : XmlIterator .forChildElements(toolbar, "tool")) { String eltName = elt.getAttribute("name"); if (eltName != null && !eltName.equals("")) { if (eltName.equals("Select Tool")) select = elt; if (eltName.equals("Wiring Tool")) wiring = elt; if (eltName.equals("Edit Tool")) edit = elt; } } if (select != null && wiring != null && edit == null) { select.setAttribute("name", "Edit Tool"); toolbar.removeChild(wiring); } } } if (version.compareTo(LogisimVersion.get(2, 6, 3)) < 0) { for (Element circElt : XmlIterator .forChildElements(root, "circuit")) { for (Element attrElt : XmlIterator.forChildElements(circElt, "a")) { String name = attrElt.getAttribute("name"); if (name != null && name.startsWith("label")) { attrElt.setAttribute("name", "c" + name); } } } repairForWiringLibrary(doc, root); repairForLegacyLibrary(doc, root); } } private Document loadXmlFrom(InputStream is) throws SAXException, IOException { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(true); DocumentBuilder builder = null; try { builder = factory.newDocumentBuilder(); } catch (ParserConfigurationException ex) { } return builder.parse(is); } LogisimFile readLibrary(InputStream is,Project proj) throws IOException, SAXException { Document doc = loadXmlFrom(is); Element elt = doc.getDocumentElement(); elt = ensureLogisimCompatibility(elt); considerRepairs(doc, elt); LogisimFile file = new LogisimFile((Loader) loader); ReadContext context = new ReadContext(file); context.toLogisimFile(elt,proj); if (file.getCircuitCount() == 0) { file.addCircuit(new Circuit("main", file,proj)); } if (context.messages.size() > 0) { StringBuilder all = new StringBuilder(); for (String msg : context.messages) { all.append(msg); all.append("\n"); } loader.showError(all.substring(0, all.length() - 1)); } return file; } private void relocateTools(Element src, Element dest, HashMap<String, String> labelMap) { if (src == null || src == dest) return; String srcLabel = src.getAttribute("name"); if (srcLabel == null) return; ArrayList<Element> toRemove = new ArrayList<Element>(); for (Element elt : XmlIterator.forChildElements(src, "tool")) { String name = elt.getAttribute("name"); if (name != null && labelMap.containsKey(srcLabel + ":" + name)) { toRemove.add(elt); } } for (Element elt : toRemove) { src.removeChild(elt); if (dest != null) { dest.appendChild(elt); } } } private void repairForLegacyLibrary(Document doc, Element root) { Element legacyElt = null; String legacyLabel = null; for (Element libElt : XmlIterator.forChildElements(root, "lib")) { String desc = libElt.getAttribute("desc"); String label = libElt.getAttribute("name"); if (desc != null && desc.equals("#Legacy")) { legacyElt = libElt; legacyLabel = label; } } if (legacyElt != null) { root.removeChild(legacyElt); ArrayList<Element> toRemove = new ArrayList<Element>(); findLibraryUses(toRemove, legacyLabel, XmlIterator.forDescendantElements(root, "comp")); boolean componentsRemoved = !toRemove.isEmpty(); findLibraryUses(toRemove, legacyLabel, XmlIterator.forDescendantElements(root, "tool")); for (Element elt : toRemove) { elt.getParentNode().removeChild(elt); } if (componentsRemoved) { String error = "Some components have been deleted;" + " the Legacy library is no longer supported."; Element elt = doc.createElement("message"); elt.setAttribute("value", error); root.appendChild(elt); } } } private void repairForWiringLibrary(Document doc, Element root) { Element oldBaseElt = null; String oldBaseLabel = null; Element gatesElt = null; String gatesLabel = null; int maxLabel = -1; Element firstLibElt = null; Element lastLibElt = null; for (Element libElt : XmlIterator.forChildElements(root, "lib")) { String desc = libElt.getAttribute("desc"); String label = libElt.getAttribute("name"); if (desc == null) { // skip these tests } else if (desc.equals("#Base")) { oldBaseElt = libElt; oldBaseLabel = label; } else if (desc.equals("#Wiring")) { // Wiring library already in file. This shouldn't happen, but if // somehow it does, we don't want to add it again. return; } else if (desc.equals("#Gates")) { gatesElt = libElt; gatesLabel = label; } if (firstLibElt == null) firstLibElt = libElt; lastLibElt = libElt; try { if (label != null) { int thisLabel = Integer.parseInt(label); if (thisLabel > maxLabel) maxLabel = thisLabel; } } catch (NumberFormatException e) { } } Element wiringElt; String wiringLabel; Element newBaseElt; String newBaseLabel; if (oldBaseElt != null) { wiringLabel = oldBaseLabel; wiringElt = oldBaseElt; wiringElt.setAttribute("desc", "#Wiring"); newBaseLabel = "" + (maxLabel + 1); newBaseElt = doc.createElement("lib"); newBaseElt.setAttribute("desc", "#Base"); newBaseElt.setAttribute("name", newBaseLabel); root.insertBefore(newBaseElt, lastLibElt.getNextSibling()); } else { wiringLabel = "" + (maxLabel + 1); wiringElt = doc.createElement("lib"); wiringElt.setAttribute("desc", "#Wiring"); wiringElt.setAttribute("name", wiringLabel); root.insertBefore(wiringElt, lastLibElt.getNextSibling()); newBaseLabel = null; newBaseElt = null; } HashMap<String, String> labelMap = new HashMap<String, String>(); addToLabelMap(labelMap, oldBaseLabel, newBaseLabel, "Poke Tool;" + "Edit Tool;Select Tool;Wiring Tool;Text Tool;Menu Tool;Text"); addToLabelMap(labelMap, oldBaseLabel, wiringLabel, "Splitter;Pin;" + "Probe;Tunnel;Clock;Pull Resistor;Bit Extender"); addToLabelMap(labelMap, gatesLabel, wiringLabel, "Constant"); relocateTools(oldBaseElt, newBaseElt, labelMap); relocateTools(oldBaseElt, wiringElt, labelMap); relocateTools(gatesElt, wiringElt, labelMap); updateFromLabelMap(XmlIterator.forDescendantElements(root, "comp"), labelMap); updateFromLabelMap(XmlIterator.forDescendantElements(root, "tool"), labelMap); } private void updateFromLabelMap(Iterable<Element> elts, HashMap<String, String> labelMap) { for (Element elt : elts) { String oldLib = elt.getAttribute("lib"); String name = elt.getAttribute("name"); if (oldLib != null && name != null) { String newLib = labelMap.get(oldLib + ":" + name); if (newLib != null) { elt.setAttribute("lib", newLib); } } } } }