org.primordion.ef.tex.Xholon2TexWirDiaO.java Source code

Java tutorial

Introduction

Here is the source code for org.primordion.ef.tex.Xholon2TexWirDiaO.java

Source

/* Xholon Runtime Framework - executes event-driven & dynamic applications
 * Copyright (C) 2017 Ken Webb
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2.1
 * of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
 */

package org.primordion.ef.tex;

import com.google.gwt.http.client.RequestBuilder;
import com.google.gwt.http.client.RequestCallback;
import com.google.gwt.http.client.RequestException;
import com.google.gwt.http.client.Request;
import com.google.gwt.http.client.Response;

import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.primordion.xholon.base.IXholon;
import org.primordion.xholon.base.PortInformation;
import org.primordion.ef.AbstractXholon2ExternalFormat;
import org.primordion.xholon.io.gwt.HtmlScriptHelper;
import org.primordion.xholon.service.ef.IXholon2GraphFormat;

/**
 * Xholon to TeX - Wiring Diagrams - Oriented.
 * Export a Xholon model to TeX TikZ/PGF format.
 * The exported content requires the "oriented WD" styles developed by David Spivak.
 * This class semi-automates the oriented Operad wiring diagrams that David Spivak currently hand codes.
 * 
 * latex options that work
\documentclass[10pt,oneside,article,landscape]{memoir}
 * 
 * TODO
 * - limited to proper output for one intermediate level
 * - try a different approach
 *  - create all the tikz nodes, and only then add the node attributes including edges
 *  - I get error messages if a referenced tikz node does not yet exist
 * - option to show ports as small quares or circles or other shape
 * - try using "above=of" of firstChild - it sort-of works
 * - optionally remove spaces from names
 * - have some makeNames() methods rather than calling node.getName(template) directly
 * - new option in Xholon.getName(template) "M_____" ?
 *  - gen a name that would display as a subscripted or superscripted name in TeX/tikz
 *  - ex: Abc:pack_23  ->  Ab_23
 * 
 * @author <a href="mailto:ken@primordion.com">Ken Webb</a>
 * @see <a href="http://www.primordion.com/Xholon">Xholon Project website</a>
 * @since 0.9.1 (Created on November 21, 2017)
 * @see <a href="https://en.wikipedia.org/wiki/PGF/TikZ">Wikipedia page</a>
 * @see <a href="http://math.mit.edu/~dspivak/">David Spivak home page</a>
 * @see <a href=""></a>
 */
@SuppressWarnings("serial")
public class Xholon2TexWirDiaO extends AbstractXholon2ExternalFormat implements IXholon2GraphFormat {

    private String outFileName;
    private String outPath = "./ef/twdo/";
    private String modelName;
    private IXholon root;

    /** Current date and time. */
    private Date timeNow;
    private long timeStamp;

    /**
     * Start of the TeX script.
     */
    protected StringBuilder startSb = null;

    /**
     * The accumulating text for all TeX nodes.
     */
    protected StringBuilder nodeSb = null;

    /**
     * The accumulating text for all TeX links (\draw statements).
     */
    protected StringBuilder linkSb = null;

    /**
     * End of the TeX script.
     */
    protected StringBuilder endSb = null;

    protected StringBuilder sb = null;

    /**
     * Each node superClass may have its own shape.
     * The map is constructed by calling getNodesStyle() for an optional list of user-specified shapes.
     */
    protected Map<String, String> shapeMap = null;

    /**
     * Constructor.
     */
    public Xholon2TexWirDiaO() {
    }

    @Override
    public String getVal_String() {
        return sb.toString();
    }

    @Override
    public boolean initialize(String outFileName, String modelName, IXholon root) {
        timeNow = new Date();
        timeStamp = timeNow.getTime();
        if (outFileName == null) {
            this.outFileName = outPath + root.getXhcName() + "_" + root.getId() + "_" + timeStamp + ".tex";
        } else {
            this.outFileName = outFileName;
        }
        this.modelName = modelName;
        this.root = root;
        this.makeShapeMap();

        this.startSb = new StringBuilder();
        if (this.isIncludeBeginEndEqu()) {
            startSb.append("\\begin{equation}\\label{").append(modelName.replace(" ", "_")).append("}\\tag{")
                    .append(modelName.replace(" ", "").replace("_", "")) // latex/pdflatex fail if tag ends with "_"
                    .append("}\n");
        }
        startSb.append("\\begin{tikzpicture}[oriented WD, bbx=1em, bby=1ex, scale=").append(this.getScale())
                .append("]\n");

        this.nodeSb = new StringBuilder().append("% nodes\n");

        this.linkSb = new StringBuilder().append("% links\n");

        this.endSb = new StringBuilder().append("\\end{tikzpicture}\n");
        if (this.isIncludeBeginEndEqu()) {
            endSb.append("\\end{equation}\n");
        }

        return true;
    }

    @Override
    public void writeAll() {
        String fn = root.getXhcName() + "_" + root.getId() + "_" + timeStamp; // + ".tex";
        this.collectInPorts(root);
        writeNode(root);
        sb = new StringBuilder();
        /*if (this.isIncludePreamble()) {
          sb.append("\\documentclass").append(this.getDocumentclass()).append("\n\n");
          // TODO getPreambleFileName()
          sb.append("% TODO  PREAMBLE GOES HERE ").append(this.getPreambleFileName()).append("\n\n");
        }*/
        if (this.isIncludeBeginEndDoc()) {
            sb.append("\\begin{document}\n");
            sb.append("\\author{");
            if ("default".equals(this.getAuthor())) {
                sb.append("AUTHOR"); // TODO
            } else {
                sb.append(this.getAuthor());
            }
            sb.append("}\n");
            sb.append("\\title{");
            if ("default".equals(this.getTitle())) {
                sb.append(modelName);
            } else {
                sb.append(this.getTitle());
            }
            sb.append("}\n");
        }
        sb.append("% ").append(modelName).append("\n")
                .append("% Automatically generated by Xholon version 0.9.1, using ")
                .append(this.getClass().getName()).append(".java\n").append("% ").append(new Date()).append(" ")
                .append(timeStamp).append("\n")
                .append("% To convert to a SVG or PDF image, save to a text (.tex) file, and run:\n")
                .append("% latex ").append(fn).append("\n") // or directly to PDF using pdflatex
                .append("% dvisvgm ").append(fn).append("\n").append("% vprerex ").append(fn).append("\n")
                //.append("% On linux (Ubuntu), select and copy this text to the system clipboard, and enter the following in a terminal window to create a PDF file:\n")
                //.append("% xclip -o > ").append(fn).append(".tex").append("; pdflatex ").append(fn).append("\n") // "evince fn.pdf" to view it in document viewer
                .append(startSb.toString()).append(nodeSb.toString()).append(linkSb.toString())
                .append(endSb.toString());
        if (this.isIncludeBeginEndDoc()) {
            sb.append("\\end{document}\n");
        }
        if (this.isIncludePreamble()) {
            StringBuilder doccSb = new StringBuilder().append(
                    "% On linux (Ubuntu), select and copy this text to the system clipboard, and enter the following in a terminal window to create a PDF file:\n")
                    .append("% xclip -o > ").append(fn).append(".tex").append("; pdflatex ").append(fn).append("\n") // "evince fn.pdf" to view it in document viewer
                    .append("% \n").append("\\documentclass").append(this.getDocumentclass()).append("\n\n")
                    .append("% This framework tex code has been provided by David Spivak.\n");
            downloadPreambleAndWriteToTarget(this.getPreambleFileName(), doccSb.toString(), sb.toString(),
                    outFileName, outPath, root);
        } else {
            writeToTarget(sb.toString(), outFileName, outPath, root);
        }
        this.removeInPorts(root);
    }

    @Override
    public void writeNode(IXholon node) {
        // do node children first
        // collect a String of child names for use by "fit"
        String fitNames = "";
        IXholon childNode = node.getFirstChild();
        while (childNode != null) {
            writeNode(childNode);
            fitNames += " (" + childNode.getName(getBbNameTemplate()) + ")";
            childNode = childNode.getNextSibling();
        }

        String nodeName = node.getName(getBbNameTemplate());
        if ((getMaxChars() != -1) && (nodeName.length() > getMaxChars())) {
            nodeName = nodeName.substring(0, getMaxChars());
        }
        int inportCount = this.getInportsCount(node);
        List<PortInformation> portList = node.getLinks(false, true);
        int outportCount = portList.size();
        if (node == this.root) {
            // at least some of the ports on the root and other container nodes, are relay nodes that are "in" rather than "out" ports
            int tempCount = inportCount;
            inportCount = outportCount;
            outportCount = tempCount;
        } else if (node.hasChildNodes()) {
            int tempCount = inportCount;
            inportCount = outportCount;
            outportCount = tempCount;
        }
        String position = "";
        String fit = "";

        IXholon prev = node.getPreviousSibling();
        if (prev != null) {
            // node is not the firstSibling
            if (prev.getXhc() == node.getXhc()) {
                // node has the same Xholon class as its previous sibling
                position = ", " + getSiblingsPosition() + " " + prev.getName(getBbNameTemplate());
            } else {
                IXholon firstSiblingWithXhClass = node.getParentNode().findFirstChildWithXhClass(prev.getXhcName());
                if (firstSiblingWithXhClass != null) {
                    position = ", " + getDiffXhtypePosition() + " "
                            + firstSiblingWithXhClass.getName(getBbNameTemplate());
                } else {
                    position = ", " + getDiffXhtypePosition() + " " + prev.getName(getBbNameTemplate());
                }
            }
        }
        if (node == this.root) {
            fit = ", fit={";
            fit += fitNames.trim();
            fit += "}";
            // ", bb name = $NAME$"
            fit += ", bb name = $" + node.getName(getBbNameTemplate()) + "$";
        } else if (node.hasChildNodes()) {
            fit = ", fit={";
            fit += fitNames.trim();
            fit += "}";
            String intermedDashing = this.getIntermedDashing();
            if ((intermedDashing != "") && (intermedDashing != "none")) {
                fit += ", " + intermedDashing;
            }
            fit += ", bb name = $" + node.getName(getBbNameTemplate()) + "$";
        } else {
            // TODO this is a first sibling that probably should not have a position
            //position = ", " + getDiffXhtypePosition() + " " + node.getParentNode().getName(getBbNameTemplate());
        }

        nodeSb.append("  \\node[bb={").append(inportCount).append("}").append("{").append(outportCount).append("}")
                //.append(", bb name=$TODO$") // should I do this ?
                .append(position).append(fit).append("]").append(" (").append(nodeName).append(")").append(" {");
        if ((node != this.root) && (!node.hasChildNodes())) {
            nodeSb.append("$").append(node.getName(getBbNameTemplate()));
            nodeSb.append("$");
        }
        nodeSb.append("}");
        String shape = this.makeShape(node);
        if (shape != null) {
            // TODO
        }
        nodeSb.append(";").append(" % ").append(node.getName(getNameTemplate())).append("\n");
        // node outgoing edges
        writeEdges(node);
    }

    @SuppressWarnings("unchecked")
    @Override
    public void writeEdges(IXholon node) {
        List<PortInformation> portList = node.getLinks(false, true);
        String outportName = "_out";
        String outportNameSuffix = "";
        if (node == this.root) {
            outportName = "_in";
            outportNameSuffix = "'";
        } else if (node.hasChildNodes()) {
            // TODO I'm assuming that a node with child nodes only has relay ports that are connected to internal nodes
            outportName = "_in";
            outportNameSuffix = "'";
        }
        for (int i = 0; i < portList.size(); i++) {
            PortInformation pi = (PortInformation) portList.get(i);
            IXholon reffedNode = pi.getReffedNode();
            if (reffedNode == null) {
                continue;
            }
            if (!reffedNode.hasAncestor(root.getName())) {
                // remoteNode is outside the scope (not a descendant) of root
                reffedNode = root; // keep the port within the scope of root by placing a relay port on root
            }
            boolean selfReffing = (reffedNode == node); // does this port point to node; is it self-referencing?
            linkSb.append("  ").append(selfReffing ? "% " : "").append("\\draw").append("[ar]") // optional ?
                    .append(" (").append(node.getName(getBbNameTemplate())).append(outportName).append(i + 1) // out string is 1-based rather than 0-based
                    .append(outportNameSuffix).append(")").append(" to").append(" (")
                    .append(reffedNode.getName(getBbNameTemplate())).append("_in")
                    .append(this.getInportsIxAndDec(reffedNode)).append(")").append(";");
            if (isShowPortName()) {
                linkSb.append(" % ").append(pi.getLocalNameNoBrackets());
            }
            linkSb.append("\n");
        }
    }

    @Override
    public void writeNodeAttributes(IXholon node) {
        // nothing to do for now
    }

    /**
     * Try to make a custom shape for a node.
     * @param node
     * @return A shape string, or null.
     */
    protected String makeShape(IXholon node) {
        if (shapeMap == null) {
            return null;
        }
        String shapeName = shapeMap.get(node.getXhcName()); // TODO also try superclasses
        //if (shapeName == null) {return null;}
        return shapeName;
    }

    /**
     * Optionally create the shapeMap, and add entries to it.
     * ex: "ellipse"
     * ex: "circle,Cable:point"
     * ex: "box,Pack:circle,Cable:point"
     */
    protected void makeShapeMap() {
        //if (!isShouldSpecifyShape()) {return;}
        String[] shapeArr = this.getNodesStyle().split(",");
        // ignore the first item in the array; this is the default
        if (shapeArr.length > 1) {
            shapeMap = new HashMap<String, String>();
            for (int i = 1; i < shapeArr.length; i++) {
                String[] entryArr = shapeArr[i].split(":");
                if (entryArr.length == 2) {
                    String entryXhcName = entryArr[0].trim();
                    String entryShapeName = entryArr[1].trim();
                    shapeMap.put(entryXhcName, entryShapeName);
                }
            }
            setNodesStyle(shapeArr[0]); // set the default shape
        }
    }

    /**
     * Collect the in ports (conjugated ports) for all nodes in the Xholon tree or in a subtree.
     */
    protected void collectInPorts(IXholon node) {
        List<PortInformation> portList = node.getLinks(false, true);
        for (int i = 0; i < portList.size(); i++) {
            PortInformation pi = (PortInformation) portList.get(i);
            if (pi != null) {
                IXholon reffedNode = pi.getReffedNode();
                String fieldName = pi.getFieldName();
                // TODO optional index
                if ((reffedNode != null) && (fieldName != null)) {
                    // create a structure in reffedNode to store the in port
                    // reffedNode.conjports = {};
                    storeInPort(reffedNode, fieldName, node);
                }
            }
        }
        // recurse
        IXholon childNode = node.getFirstChild();
        while (childNode != null) {
            collectInPorts(childNode);
            childNode = childNode.getNextSibling();
        }
    }

    protected void removeInPorts(IXholon node) {
        this.removeInPort(node);
        // recurse
        IXholon childNode = node.getFirstChild();
        while (childNode != null) {
            removeInPorts(childNode);
            childNode = childNode.getNextSibling();
        }
    }

    protected native void storeInPort(IXholon reffedNode, String fieldName, IXholon thisNode) /*-{
                                                                                              if (!reffedNode["inports"]) {
                                                                                              reffedNode["inports"] = 0;
                                                                                              reffedNode["inportsIx"] = 0;
                                                                                              }
                                                                                              reffedNode["inports"]++;
                                                                                              reffedNode["inportsIx"]++;
                                                                                              }-*/;

    protected native void removeInPort(IXholon node) /*-{
                                                     if (typeof node["inports"] !== "undefined") {
                                                     delete(node["inports"]);
                                                     }
                                                     if (typeof node["inportsIx"] !== "undefined") {
                                                     delete(node["inportsIx"]);
                                                     }
                                                     }-*/;

    protected native int getInportsCount(IXholon node) /*-{
                                                       if (node["inports"]) {
                                                       return node["inports"];
                                                       }
                                                       else {
                                                       return 0;
                                                       }
                                                       }-*/;

    protected native int getInportsIxAndDec(IXholon node) /*-{
                                                          var inportsCount = 0;
                                                          if (node["inportsIx"]) {
                                                          inportsCount = node["inportsIx"];
                                                          node["inportsIx"]--;
                                                          }
                                                          return inportsCount;
                                                          }-*/;

    /*
     * Download the preamble contents, insert it between part1 and part2, and write it all to the target.
     */
    protected void downloadPreambleAndWriteToTarget(String preambleFileName, String part1, String part2,
            String outFileName, String outPath, IXholon root) {
        try {
            //final String _contentType = contentType;
            final IXholon _root = root;
            new RequestBuilder(RequestBuilder.GET, preambleFileName).sendRequest("", new RequestCallback() {
                @Override
                public void onResponseReceived(Request req, Response resp) {
                    if (resp.getStatusCode() == resp.SC_OK) {
                        //root.println(resp.getText());
                        writeToTarget(part1 + resp.getText() + part2, outFileName, outPath, _root);
                    } else {
                        //root.println("status code:" + resp.getStatusCode());
                        //root.println("status text:" + resp.getStatusText());
                        //root.println("text:\n" + resp.getText());
                        writeToTarget(part1 + "% PREAMBLE" + preambleFileName + " status code:"
                                + resp.getStatusCode() + " status text:" + resp.getStatusText() + "text:"
                                + resp.getText() + "\n" + part2, outFileName, outPath, _root);
                    }
                }

                @Override
                public void onError(Request req, Throwable e) {
                    //root.println("onError:" + e.getMessage());
                    writeToTarget(
                            part1 + "% PREAMBLE" + preambleFileName + " onError:" + e.getMessage() + "\n" + part2,
                            outFileName, outPath, _root);
                }
            });
        } catch (RequestException e) {
            //root.println("RequestException:" + e.getMessage());
            writeToTarget(
                    part1 + "% PREAMBLE" + preambleFileName + " RequestException:" + e.getMessage() + "\n" + part2,
                    outFileName, outPath, root);
        }
    }

    /** Node name template */
    public native String getNameTemplate() /*-{return this.efParams.nameTemplate;}-*/;

    public native void setNameTemplate(String nameTemplate) /*-{this.efParams.nameTemplate = nameTemplate;}-*/;

    /** bb name template */
    public native String getBbNameTemplate() /*-{return this.efParams.bbNameTemplate;}-*/;

    public native void setBbNameTemplate(
            String nameTemplate) /*-{this.efParams.bbNameTemplate = bbNameTemplate;}-*/;

    /** Number of characters to show in the node name */
    public native int getMaxChars() /*-{return this.efParams.maxChars;}-*/;

    public native void setMaxChars(int maxChars) /*-{this.efParams.maxChars = maxChars;}-*/;

    /**
     * Make a JavaScript object with all the parameters for this external format.
     */
    protected native void makeEfParams() /*-{
                                         var p = {};
                                         p.scale = 1; // 1 2 0.5
                                         p.nameTemplate = "r:c_i"; // "r:c_i^" "R^^^^^"
                                         p.bbNameTemplate = "R^^^^^";
                                         p.maxChars = -1; // -1 1 3
                                         p.showPortName = false; // as a comment
                                         p.nodesStyle = "rectangle"; // rectangle
                                         p.linksStyle = "default"; // default
                                         p.siblingsPosition = "right=of";
                                         p.diffXhtypePosition = "below=of";
                                         p.bipartite = false; // ex: Packs and Cables
                                         p.intermedDashing = "loosely dotted"; // dashed,dotted,none,loosely dashed,densely dashed,loosely dotted,densely dotted,solid(default)
                                         p.includePreamble = true; 
                                         p.documentclass = "[10pt,oneside,article,landscape]{memoir}"; // content of \documentclass line, the first line in the preamble
                                         p.preambleFileName = "texWirDiaOpreamble.tex";
                                         p.includeBeginEndDoc = true; // whether to include \begin{document} and \end{document} and \title{...} and \author{...}
                                         p.title = "default"; // default is model name
                                         p.author = "default"; // default is GWT user
                                         p.includeBeginEndEqu = true; // whether or not to include \begin{equation} and \end{equation}
                                         this.efParams = p;
                                         }-*/;

    public native boolean isIncludePreamble() /*-{return this.efParams.includePreamble;}-*/;
    //public native void setIncludePreamble(boolean includePreamble) /*-{this.efParams.includePreamble = includePreamble;}-*/;

    public native String getDocumentclass() /*-{return this.efParams.documentclass;}-*/;
    //public native void setDocumentclass(String documentclass) /*-{this.efParams.documentclass = documentclass;}-*/;

    public native String getPreambleFileName() /*-{return this.efParams.preambleFileName;}-*/;
    //public native void setPreambleFileName(String preambleFileName) /*-{this.efParams.preambleFileName = preambleFileName;}-*/;

    public native boolean isIncludeBeginEndDoc() /*-{return this.efParams.includeBeginEndDoc;}-*/;
    //public native void setIncludeBeginEndDoc(boolean includeBeginEndDoc) /*-{this.efParams.includeBeginEndDoc = includeBeginEndDoc;}-*/;

    public native String getTitle() /*-{return this.efParams.title;}-*/;
    //public native void setTitle(String title) /*-{this.efParams.title = title;}-*/;

    public native String getAuthor() /*-{return this.efParams.author;}-*/;
    //public native void setAuthor(String author) /*-{this.efParams.author = author;}-*/;

    public native boolean isIncludeBeginEndEqu() /*-{return this.efParams.includeBeginEndEqu;}-*/;
    //public native void setIncludeBeginEndEqu(boolean includeBeginEndEqu) /*-{this.efParams.includeBeginEndEqu = includeBeginEndEqu;}-*/;

    public native double getScale() /*-{return this.efParams.scale;}-*/;
    //public native void setScale(double scale) /*-{this.efParams.scale = scale;}-*/;

    /**
     * Whether or not a link should show the name of the port (as a comment).
     */
    public native boolean isShowPortName() /*-{return this.efParams.showPortName;}-*/;
    //public native void setShowPortName(boolean showPortName) /*-{this.efParams.showPortName = showPortName;}-*/;

    // rect text image
    public native String getNodesStyle() /*-{return this.efParams.nodesStyle;}-*/;

    public native void setNodesStyle(String nodesStyle) /*-{this.efParams.nodesStyle = nodesStyle;}-*/;

    public native String getLinksStyle() /*-{return this.efParams.linksStyle;}-*/;
    //public native void setLinksStyle(String linksStyle) /*-{this.efParams.linksStyle = linksStyle;}-*/;

    public native String getSiblingsPosition() /*-{return this.efParams.siblingsPosition;}-*/;
    //public native void setSiblingsPosition(String siblingsPosition) /*-{this.efParams.siblingsPosition = siblingsPosition;}-*/;

    public native String getDiffXhtypePosition() /*-{return this.efParams.diffXhtypePosition;}-*/;
    //public native void setDiffXhtypePosition(String diffXhtypePosition) /*-{this.efParams.diffXhtypePosition = diffXhtypePosition;}-*/;

    public native boolean isBipartite() /*-{return this.efParams.bipartite;}-*/;
    //public native void setBipartite(boolean bipartite) /*-{this.efParams.bipartite = bipartite;}-*/;

    public native String getIntermedDashing() /*-{return this.efParams.intermedDashing;}-*/;

    public native void setIntermedDashing(
            String intermedDashing) /*-{this.efParams.intermedDashing = intermedDashing;}-*/;

}