/*******************************************************************************
* 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.OutputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
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.Wire;
import com.cburch.logisim.comp.Component;
import com.cburch.logisim.comp.ComponentFactory;
import com.cburch.logisim.data.Attribute;
import com.cburch.logisim.data.AttributeDefaultProvider;
import com.cburch.logisim.data.AttributeSet;
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 XmlWriter {
static void write(LogisimFile file, OutputStream out, LibraryLoader loader,
File destFile) throws ParserConfigurationException,
TransformerConfigurationException, TransformerException {
DocumentBuilderFactory docFactory = DocumentBuilderFactory
.newInstance();
DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
Document doc = docBuilder.newDocument();
XmlWriter context;
if (destFile != null) {
String dstFilePath = destFile.getAbsolutePath();
dstFilePath = dstFilePath.substring(0,
dstFilePath.lastIndexOf(File.separator));
context = new XmlWriter(file, doc, loader, dstFilePath);
} else
context = new XmlWriter(file, doc, loader);
context.fromLogisimFile();
TransformerFactory tfFactory = TransformerFactory.newInstance();
try {
tfFactory.setAttribute("indent-number", Integer.valueOf(2));
} catch (IllegalArgumentException e) {
}
Transformer tf = tfFactory.newTransformer();
tf.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
tf.setOutputProperty(OutputKeys.INDENT, "yes");
try {
tf.setOutputProperty("{http://xml.apache.org/xslt}indent-amount",
"2");
} catch (IllegalArgumentException e) {
}
Source src = new DOMSource(doc);
Result dest = new StreamResult(out);
tf.transform(src, dest);
}
private LogisimFile file;
private Document doc;
/**
* Path of the file which is being written on disk -- used to relativize
* components stored in it
*/
private String outFilepath;
private LibraryLoader loader;
private HashMap<Library, String> libs = new HashMap<Library, String>();
private XmlWriter(LogisimFile file, Document doc, LibraryLoader loader) {
this(file, doc, loader, null);
}
private XmlWriter(LogisimFile file, Document doc, LibraryLoader loader,
String outFilepath) {
this.file = file;
this.doc = doc;
this.loader = loader;
this.outFilepath = outFilepath;
}
void addAttributeSetContent(Element elt, AttributeSet attrs,
AttributeDefaultProvider source) {
if (attrs == null)
return;
LogisimVersion ver = Main.VERSION;
if (source != null && source.isAllDefaultValues(attrs, ver))
return;
for (Attribute<?> attrBase : attrs.getAttributes()) {
@SuppressWarnings("unchecked")
Attribute<Object> attr = (Attribute<Object>) attrBase;
Object val = attrs.getValue(attr);
if (attrs.isToSave(attr) && val != null) {
Object dflt = source == null ? null : source
.getDefaultAttributeValue(attr, ver);
if (dflt == null || !dflt.equals(val)) {
Element a = doc.createElement("a");
a.setAttribute("name", attr.getName());
String value = attr.toStandardString(val);
if (attr.getName().equals("filePath")
&& outFilepath != null) {
Path outFP = Paths.get(outFilepath);
Path attrValP = Paths.get(value);
value = (outFP.relativize(attrValP)).toString();
a.setAttribute("val", value);
} else {
if (value.indexOf("\n") >= 0) {
a.appendChild(doc.createTextNode(value));
} else {
a.setAttribute("val", attr.toStandardString(val));
}
}
elt.appendChild(a);
}
}
}
}
Library findLibrary(ComponentFactory source) {
if (file.contains(source)) {
return file;
}
for (Library lib : file.getLibraries()) {
if (lib.contains(source))
return lib;
}
return null;
}
Library findLibrary(Tool tool) {
if (libraryContains(file, tool)) {
return file;
}
for (Library lib : file.getLibraries()) {
if (libraryContains(lib, tool))
return lib;
}
return null;
}
Element fromCircuit(Circuit circuit) {
Element ret = doc.createElement("circuit");
ret.setAttribute("name", circuit.getName());
addAttributeSetContent(ret, circuit.getStaticAttributes(), null);
if (!circuit.getAppearance().isDefaultAppearance()) {
Element appear = doc.createElement("appear");
for (Object o : circuit.getAppearance().getObjectsFromBottom()) {
if (o instanceof AbstractCanvasObject) {
Element elt = ((AbstractCanvasObject) o).toSvgElement(doc);
if (elt != null) {
appear.appendChild(elt);
}
}
}
ret.appendChild(appear);
}
for (Wire w : circuit.getWires()) {
ret.appendChild(fromWire(w));
}
for (Component comp : circuit.getNonWires()) {
Element elt = fromComponent(comp);
if (elt != null)
ret.appendChild(elt);
}
return ret;
}
Element fromComponent(Component comp) {
ComponentFactory source = comp.getFactory();
Library lib = findLibrary(source);
String lib_name;
if (lib == null) {
loader.showError(source.getName() + " component not found");
return null;
} else if (lib == file) {
lib_name = null;
} else {
lib_name = libs.get(lib);
if (lib_name == null) {
loader.showError("unknown library within file");
return null;
}
}
Element ret = doc.createElement("comp");
if (lib_name != null)
ret.setAttribute("lib", lib_name);
ret.setAttribute("name", source.getName());
ret.setAttribute("loc", comp.getLocation().toString());
addAttributeSetContent(ret, comp.getAttributeSet(), comp.getFactory());
return ret;
}
Element fromLibrary(Library lib) {
Element ret = doc.createElement("lib");
if (libs.containsKey(lib))
return null;
String name = "" + libs.size();
String desc = loader.getDescriptor(lib);
if (desc == null) {
loader.showError("library location unknown: " + lib.getName());
return null;
}
libs.put(lib, name);
ret.setAttribute("name", name);
ret.setAttribute("desc", desc);
for (Tool t : lib.getTools()) {
AttributeSet attrs = t.getAttributeSet();
if (attrs != null) {
Element toAdd = doc.createElement("tool");
toAdd.setAttribute("name", t.getName());
addAttributeSetContent(toAdd, attrs, t);
if (toAdd.getChildNodes().getLength() > 0) {
ret.appendChild(toAdd);
}
}
}
return ret;
}
Element fromLogisimFile() {
Element ret = doc.createElement("project");
doc.appendChild(ret);
ret.appendChild(doc
.createTextNode("\nThis file is intended to be "
+ "loaded by Logisim-evolution (https://github.com/reds-heig/logisim-evolution).\n"));
ret.setAttribute("version", "1.0");
ret.setAttribute("source", Main.VERSION_NAME);
for (Library lib : file.getLibraries()) {
Element elt = fromLibrary(lib);
if (elt != null)
ret.appendChild(elt);
}
if (file.getMainCircuit() != null) {
Element mainElt = doc.createElement("main");
mainElt.setAttribute("name", file.getMainCircuit().getName());
ret.appendChild(mainElt);
}
ret.appendChild(fromOptions());
ret.appendChild(fromMouseMappings());
ret.appendChild(fromToolbarData());
for (Circuit circ : file.getCircuits()) {
ret.appendChild(fromCircuit(circ));
}
return ret;
}
Element fromMouseMappings() {
Element elt = doc.createElement("mappings");
MouseMappings map = file.getOptions().getMouseMappings();
for (Map.Entry<Integer, Tool> entry : map.getMappings().entrySet()) {
Integer mods = entry.getKey();
Tool tool = entry.getValue();
Element toolElt = fromTool(tool);
String mapValue = InputEventUtil.toString(mods.intValue());
toolElt.setAttribute("map", mapValue);
elt.appendChild(toolElt);
}
return elt;
}
Element fromOptions() {
Element elt = doc.createElement("options");
addAttributeSetContent(elt, file.getOptions().getAttributeSet(), null);
return elt;
}
Element fromTool(Tool tool) {
Library lib = findLibrary(tool);
String lib_name;
if (lib == null) {
loader.showError(StringUtil.format("tool `%s' not found",
tool.getDisplayName()));
return null;
} else if (lib == file) {
lib_name = null;
} else {
lib_name = libs.get(lib);
if (lib_name == null) {
loader.showError("unknown library within file");
return null;
}
}
Element elt = doc.createElement("tool");
if (lib_name != null)
elt.setAttribute("lib", lib_name);
elt.setAttribute("name", tool.getName());
addAttributeSetContent(elt, tool.getAttributeSet(), tool);
return elt;
}
Element fromToolbarData() {
Element elt = doc.createElement("toolbar");
ToolbarData toolbar = file.getOptions().getToolbarData();
for (Tool tool : toolbar.getContents()) {
if (tool == null) {
elt.appendChild(doc.createElement("sep"));
} else {
elt.appendChild(fromTool(tool));
}
}
return elt;
}
Element fromWire(Wire w) {
Element ret = doc.createElement("wire");
ret.setAttribute("from", w.getEnd0().toString());
ret.setAttribute("to", w.getEnd1().toString());
return ret;
}
boolean libraryContains(Library lib, Tool query) {
for (Tool tool : lib.getTools()) {
if (tool.sharesSource(query))
return true;
}
return false;
}
}