# -*- coding: utf-8 -*-
"""Convert graphviz graphs to LaTeX-friendly formats

Various tools for converting graphs generated by the graphviz library
to formats for use with LaTeX.

Copyright (c) 2006-2019, Kjell Magne Fauske

"""

# Copyright (c) 2006-2019, Kjell Magne Fauske
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.

__author__ = 'Kjell Magne Fauske'
__version__ = '2.11.3'
__license__ = 'MIT'

import argparse
import os.path as path
import sys, tempfile, os, re
import logging
import warnings
from subprocess import Popen, PIPE

from . import dotparsing

# initialize logging module
log = logging.getLogger("dot2tex")

DEFAULT_TEXTENCODING = 'utf8'
DEFAULT_OUTPUT_FORMAT = 'pgf'

# label margins in inches
DEFAULT_LABEL_XMARGIN = 0.11
DEFAULT_LABEL_YMARGIN = 0.055
DEFAULT_EDGELABEL_XMARGIN = 0.01
DEFAULT_EDGELABEL_YMARGIN = 0.01
DEFAULT_GRAPHLABEL_XMARGIN = 0.01
DEFAULT_GRAPHLABEL_YMARGIN = 0.01

DEFAULT_NODE_WIDTH = 0.75
DEFAULT_NODE_HEIGHT = 0.5

# Todo: set papersize based on bb
# Todo: Fontcolor

# Todo: Support linewidth in draw string
# Todo: Support linestyle in draw string
# Todo: Need to reconsider edge draw order.
# See for instance html2.xdot

# Inch to bp conversion factor
INCH2BP = 72.0

# Examples of draw strings
# c 5 -black F 14.000000 11 -Times-Roman T 99 159 0 44 8 -a_1 test


SPECIAL_CHARS = ['$', '\\', '%', '_', '#', '{', r'}', '^', '&']
SPECIAL_CHARS_REPLACE = [r'\$', r'$\backslash$', r'\%', r'\_', r'\#',
                         r'\{', r'\}', r'\^{}', r'\&']
charmap = dict(zip(SPECIAL_CHARS, SPECIAL_CHARS_REPLACE))

helpmsg = """Failed to parse the input data. Is it a valid dot file?
Try to input xdot data directly. Example:
    dot -Txdot file.dot | dot2tex > file.tex

If this does not work, check that you have an updated version of PyParsing and
Graphviz. Users have reported problems with old versions. You can also run
dot2tex in debug mode using the --debug option:
    dot2tex --debug file.dot
A file dot2tex.log will be written to the current directory with detailed
information useful for debugging."""


def mreplace(s, chararray, newchararray):
    for a, b in zip(chararray, newchararray):
        s = s.replace(a, b)
    return s


def escape_texchars(string):
    r"""Escape the special LaTeX-chars %{}_^

    Examples:

    >>> escape_texchars('10%')
    '10\\%'
    >>> escape_texchars('%{}_^\\$')
    '\\%\\{\\}\\_\\^{}$\\backslash$\\$'
    """
    return "".join([charmap.get(c, c) for c in string])


def tikzify(s):
    if s.strip():
        return mreplace(s, r'\,:.()', '-+_*{}')
    else:
        return "d2tnn%i" % (len(s) + 1)


def nsplit(seq, n=2):
    """Split a sequence into pieces of length n

    If the length of the sequence isn't a multiple of n, the rest is discarded.
    Note that nsplit will strings into individual characters.

    Examples:
    >>> nsplit('aabbcc')
    [('a', 'a'), ('b', 'b'), ('c', 'c')]
    >>> nsplit('aabbcc',n=3)
    [('a', 'a', 'b'), ('b', 'c', 'c')]

    # Note that cc is discarded
    >>> nsplit('aabbcc',n=4)
    [('a', 'a', 'b', 'b')]
    """
    return [xy for xy in zip(*[iter(seq)] * n)]


def chunks(s, cl):
    """Split a string or sequence into pieces of length cl and return an iterator
    """
    for i in range(0, len(s), cl):
        yield s[i:i + cl]


def replace_tags(template, tags, tagsreplace):
    """Replace occurrences of tags with tagsreplace

    Example:
    >>> replace_tags('a b c d',('b','d'),{'b':'bbb','d':'ddd'})
    'a bbb c ddd'
    """
    s = template
    for tag in tags:
        replacestr = tagsreplace.get(tag, '')
        if not replacestr:
            replacestr = ''
        s = s.replace(tag, replacestr)
    return s


def getboolattr(item, key, default):
    if str(getattr(item, key, '')).lower() == 'true':
        return True
    else:
        return False


def smart_float(number):
    number_as_string = "%s" % float(number)
    if 'e' in number_as_string:
        return "%.4f" % float(number)
    else:
        return number_as_string


def is_multiline_label(drawobject):
    # https://graphviz.gitlab.io/_pages/doc/info/attrs.html#k:escString
    if getattr(drawobject, "texlbl", None):
        return False

    label = getattr(drawobject, "label", "")
    return any(x in label for x in [r"\n", r"\l", r"\r"])


def create_xdot(dotdata, prog='dot', options=''):
    """Run a graph through Graphviz and return an xdot-version of the graph"""
    # The following code is from the pydot module written by Ero Carrera
    progs = dotparsing.find_graphviz()

    # prog = 'dot'
    if progs is None:
        log.error('Could not locate Graphviz binaries')
        return None
    if not prog in progs:
        log.error('Invalid prog=%s', prog)
        raise NameError('The %s program is not recognized. Valid values are %s' % (prog, list(progs)))

    tmp_fd, tmp_name = tempfile.mkstemp()
    os.close(tmp_fd)
    if os.sys.version_info[0] >= 3:
        with open(tmp_name, 'w', encoding="utf8") as f:
            f.write(dotdata)
    else:
        with open(tmp_name, 'w') as f:
            f.write(dotdata)
    output_format = 'xdot'
    progpath = '"%s"' % progs[prog].strip()
    cmd = progpath + ' -T' + output_format + ' ' + options + ' ' + tmp_name
    log.debug('Creating xdot data with: %s', cmd)
    p = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE, close_fds=(sys.platform != 'win32'))
    (stdout, stderr) = (p.stdout, p.stderr)
    try:
        data = stdout.read()
    finally:
        stdout.close()

    try:
        error_data = stderr.read()
        if error_data:
            if b'Error:' in error_data:
                log.error("Graphviz returned with the following message: %s", error_data)
            else:
                # Graphviz raises a lot of warnings about too small labels,
                # we therefore log them using log.debug to "hide" them
                log.debug('Graphviz STDERR %s', error_data)
    finally:
        stderr.close()
    p.kill()
    p.wait()

    os.unlink(tmp_name)
    return data


def parse_dot_data(dotdata):
    """Wrapper for pydot.graph_from_dot_data

    Redirects error messages to the log.
    """
    parser = dotparsing.DotDataParser()
    try:
        graph = parser.parse_dot_data(dotdata)
    except dotparsing.ParseException:
        raise
    finally:
        del parser
    log.debug('Parsed graph:\n%s', str(graph))
    return graph


def parse_drawstring(drawstring):
    """Parse drawstring and returns a list of draw operations"""

    # The draw string parser is a bit clumsy and slow
    def doeE(c, s):
        """Parse ellipse"""
        # E x0 y0 w h  Filled ellipse ((x-x0)/w)^2 + ((y-y0)/h)^2 = 1
        # e x0 y0 w h  Unfilled ellipse ((x-x0)/w)^2 + ((y-y0)/h)^2 = 1
        tokens = s.split()[0:4]
        if not tokens:
            return None
        points = [float(t) for t in tokens]
        didx = sum(len(t) for t in tokens) + len(points) + 1
        return didx, (c, points[0], points[1], points[2], points[3])

    def doPLB(c, s):
        """Parse polygon, polyline og B-spline"""
        # P n x1 y1 ... xn yn  Filled polygon using the given n points
        # p n x1 y1 ... xn yn  Unfilled polygon using the given n points
        # L n x1 y1 ... xn yn  Polyline using the given n points
        # B n x1 y1 ... xn yn  B-spline using the given n control points
        # b n x1 y1 ... xn yn  Filled B-spline using the given n control points
        tokens = s.split()
        n = int(tokens[0])
        points = [float(t) for t in tokens[1:n * 2 + 1]]
        didx = sum(len(t) for t in tokens[1:n * 2 + 1]) + n * 2 + 2
        npoints = nsplit(points, 2)
        return didx, (c, npoints)

    def doCS(c, s):
        """Parse fill or pen color"""
        # C n -c1c2...cn  Set fill color.
        # c n -c1c2...cn  Set pen color.
        # Graphviz uses the following color formats:
        # "#%2x%2x%2x"    Red-Green-Blue (RGB)
        #   "#%2x%2x%2x%2x" Red-Green-Blue-Alpha (RGBA)
        #   H[, ]+S[, ]+V   Hue-Saturation-Value (HSV) 0.0 <= H,S,V <= 1.0
        #   string  color name
        tokens = s.split()
        n = int(tokens[0])
        tmp = len(tokens[0]) + 3
        d = s[tmp:tmp + n]
        didx = len(d) + tmp + 1
        return didx, (c, d)

    def doFont(c, s):
        # F s n -c1c2...cn
        # Set font. The font size is s points. The font name consists of
        # the n characters following '-'.
        tokens = s.split()
        size = tokens[0]
        n = int(tokens[1])
        tmp = len(size) + len(tokens[1]) + 4
        d = s[tmp:tmp + n]
        didx = len(d) + tmp
        return didx, (c, size, d)

    def doText(c, s):
        # T x y j w n -c1c2...cn
        # Text drawn using the baseline point (x,y). The text consists of the
        # n characters following '-'. The text should be left-aligned
        # (centered, right-aligned) on the point if j is -1 (0, 1), respectively.
        # The value w gives the width of the text as computed by the library.
        tokens = s.split()
        x, y, j, w = tokens[0:4]
        n = int(tokens[4])
        tmp = sum(len(t) for t in tokens[0:5]) + 7
        text = s[tmp:tmp + n]
        didx = len(text) + tmp
        return didx, [c, x, y, j, w, text]

    cmdlist = []
    stat = {}
    idx = 0
    s = drawstring.strip().replace('\\', '')
    while idx < len(s) - 1:
        didx = 1
        c = s[idx]
        stat[c] = stat.get(c, 0) + 1
        try:
            if c in ('e', 'E'):
                didx, cmd = doeE(c, s[idx + 1:])
                cmdlist.append(cmd)
            elif c in ('p', 'P', 'L', 'b', 'B'):
                didx, cmd = doPLB(c, s[idx + 1:])
                cmdlist.append(cmd)
            elif c in ('c', 'C', 'S'):
                didx, cmd = doCS(c, s[idx + 1:])
                cmdlist.append(cmd)
            elif c == 'F':
                didx, cmd = doFont(c, s[idx + 1:])
                cmdlist.append(cmd)
            elif c == 'T':
                didx, cmd = doText(c, s[idx + 1:])
                cmdlist.append(cmd)
        except Exception as err:
            log.debug("Failed to parse drawstring %s\n%s", s, err.message)

        idx += didx
    return cmdlist, stat


def get_graphlist(gg, l=None):
    """Traverse a graph with subgraphs and return them as a list"""
    if not l:
        l = []
        outer = True
    else:
        outer = False
    l.append(gg)
    if gg.get_subgraphs():
        for g in gg.get_subgraphs():
            get_graphlist(g, l)
    if outer:
        return l


class EndOfGraphElement(object):
    def __init__(self):
        pass


def get_all_graph_elements(graph, l=None):
    """Return all nodes and edges, including elements in subgraphs"""
    if not l:
        l = []
        outer = True
        l.append(graph)
    else:
        outer = False
    for element in graph.allitems:
        if isinstance(element, dotparsing.DotSubGraph):
            l.append(element)
            get_all_graph_elements(element, l)
        else:
            l.append(element)

    if outer:
        return l
    else:
        l.append(EndOfGraphElement())


class DotConvBase(object):
    """Dot2TeX converter base"""

    def __init__(self, options=None):
        self.color = ""
        self.opacity = None
        try:
            self.template
        except AttributeError:
            self.template = options.get('template', '')
        self.textencoding = options.get('encoding', DEFAULT_TEXTENCODING)
        self.templatevars = {}
        self.body = ""
        if options.get('templatefile', ''):
            self.load_template(options['templatefile'])
        if options.get('template', ''):
            self.template = options['template']

        self.options = options or {}
        if options.get('texpreproc') or options.get('autosize'):
            self.dopreproc = True
        else:
            self.dopreproc = False

    def load_template(self, templatefile):
        try:
            with open(templatefile) as f:
                self.template = f.read()
        except:
            pass

    def convert_file(self, filename):
        """Load dot file and convert"""
        pass

    def start_fig(self):
        return ""

    def end_fig(self):
        return ""

    def draw_ellipse(self, drawop, style=None):
        return ""

    def draw_bezier(self, drawop, style=None):
        return ""

    def draw_polygon(self, drawop, style=None):
        return ""

    def draw_polyline(self, drawop, style=None):
        return ""

    def draw_text(self, drawop, style=None):
        return ""

    def output_node_comment(self, node):
        return "  %% Node: %s\n" % node.name

    def output_edge_comment(self, edge):
        src = edge.get_source()
        dst = edge.get_destination()
        if self.directedgraph:
            edge = '->'
        else:
            edge = '--'
        return "  %% Edge: %s %s %s\n" % (src, edge, dst)

    def set_color(self, node):
        return ""

    def set_style(self, node):
        return ""

    def draw_edge(self, edge):
        return ""

    def start_node(self, node):
        return ""

    def end_node(self, node):
        return ""

    def start_graph(self, graph):
        return ""

    def end_graph(self, graph):
        return ""

    def start_edge(self):
        return ""

    def end_edge(self):
        return ""

    def filter_styles(self, style):
        return style

    def convert_color(self, drawopcolor, pgf=False):
        """Convert color to a format usable by LaTeX and XColor"""
        # Graphviz uses the following color formats:
        # "#%2x%2x%2x"    Red-Green-Blue (RGB)
        #   "#%2x%2x%2x%2x" Red-Green-Blue-Alpha (RGBA)
        #   H[, ]+S[, ]+V   Hue-Saturation-Value (HSV) 0.0 <= H,S,V <= 1.0
        #   string  color name

        # Is the format RBG(A)?
        if drawopcolor.startswith('#'):
            t = list(chunks(drawopcolor[1:], 2))
            # parallell lines not yet supported
            if len(t) > 6:
                t = t[0:3]
            rgb = [(round((int(n, 16) / 255.0), 2)) for n in t]
            if pgf:
                colstr = "{rgb}{%s,%s,%s}" % tuple(rgb[0:3])
                opacity = "1"
                if len(rgb) == 4:
                    opacity = rgb[3]
                return colstr, opacity
            else:
                return "[rgb]{%s,%s,%s}" % tuple(rgb[0:3])

        elif (len(drawopcolor.split(' ')) == 3) or (len(drawopcolor.split(',')) == 3):
            # are the values space or comma separated?
            hsb = drawopcolor.split(',')
            if not len(hsb) == 3:
                hsb = drawopcolor.split(' ')
            if pgf:
                return "{hsb}{%s,%s,%s}" % tuple(hsb)
            else:
                return "[hsb]{%s,%s,%s}" % tuple(hsb)
        else:
            drawopcolor = drawopcolor.replace('grey', 'gray')
            drawopcolor = drawopcolor.replace('_', '')
            drawopcolor = drawopcolor.replace(' ', '')
            return drawopcolor

    def do_drawstring(self, drawstring, drawobj, texlbl_name="texlbl", use_drawstring_pos=False):
        """Parse and draw drawsting

        Just a wrapper around do_draw_op.
        """
        drawoperations, stat = parse_drawstring(drawstring)
        return self.do_draw_op(drawoperations, drawobj, stat, texlbl_name, use_drawstring_pos)

    def do_draw_op(self, drawoperations, drawobj, stat, texlbl_name="texlbl", use_drawstring_pos=False):
        """Excecute the operations in drawoperations"""
        s = ""
        for drawop in drawoperations:
            op = drawop[0]
            style = getattr(drawobj, 'style', None)
            # styles are not passed to the draw operations in the
            # duplicate mode
            if style and not self.options.get('duplicate'):
                # map Graphviz styles to backend styles
                style = self.filter_styles(style)
                styles = [self.styles.get(key.strip(), key.strip())
                          for key in style.split(',') if key]
                style = ','.join(styles)
            else:
                style = None

            if op in ['e', 'E']:
                s += self.draw_ellipse(drawop, style)
            elif op in ['p', 'P']:
                s += self.draw_polygon(drawop, style)
            elif op == 'L':
                s += self.draw_polyline(drawop, style)
            elif op in ['C', 'c']:
                s += self.set_color(drawop)
            elif op == 'S':
                s += self.set_style(drawop)
            elif op in ['B']:
                s += self.draw_bezier(drawop, style)
            elif op in ['T']:
                # Need to decide what to do with the text
                # Note that graphviz removes the \ character from the draw
                # string. Use \\ instead
                # Todo: Use text from node|edge.label or name
                # Todo: What about multiline labels?
                text = drawop[5]
                # head and tail label
                texmode = self.options.get('texmode', 'verbatim')
                if drawobj.attr.get('texmode', ''):
                    texmode = drawobj.attr['texmode']
                if texlbl_name in drawobj.attr:
                    # the texlbl overrides everything
                    text = drawobj.attr[texlbl_name]
                elif texmode == 'verbatim':
                    # verbatim mode
                    text = escape_texchars(text)
                    pass
                elif texmode == 'math':
                    # math mode
                    text = "$%s$" % text

                drawop[5] = text
                if self.options.get('alignstr', ''):
                    drawop.append(self.options.get('alignstr'))
                if stat['T'] == 1 and \
                        self.options.get('valignmode', 'center') == 'center':
                    # do this for single line only
                    # force centered alignment
                    drawop[3] = '0'
                    if not use_drawstring_pos:
                        if texlbl_name == "tailtexlbl":
                            pos = drawobj.attr.get('tail_lp') or \
                                  drawobj.attr.get('pos')
                        elif texlbl_name == "headtexlbl":
                            pos = drawobj.attr.get('head_lp') or \
                                  drawobj.attr.get('pos')
                        else:
                            pos = drawobj.attr.get('lp') or \
                                  drawobj.attr.get('pos')

                        if pos:
                            coord = pos.split(',')
                            if len(coord) == 2:
                                drawop[1] = coord[0]
                                drawop[2] = coord[1]
                            pass
                lblstyle = drawobj.attr.get('lblstyle')
                exstyle = drawobj.attr.get('exstyle', '')
                if exstyle:
                    if lblstyle:
                        lblstyle += ',' + exstyle
                    else:
                        lblstyle = exstyle
                s += self.draw_text(drawop, lblstyle)
        return s

    def do_nodes(self):
        s = ""
        for node in self.nodes:
            self.currentnode = node
            general_draw_string = node.attr.get('_draw_', "")
            label_string = node.attr.get('_ldraw_', "")

            drawstring = general_draw_string + " " + label_string

            if not drawstring.strip():
                continue
            # detect node type
            shape = node.attr.get('shape', '')
            if not shape:
                shape = 'ellipse'  # default
                # extract size information
            x, y = node.attr.get('pos', '').split(',')

            # width and height are in inches. Convert to bp units
            # w = float(node.attr['width']) * INCH2BP
            # h = float(node.attr['height']) * INCH2BP

            s += self.output_node_comment(node)
            s += self.start_node(node)
            s += self.do_drawstring(drawstring, node)
            s += self.end_node(node)
        self.body += s

    def get_edge_points(self, edge):
        # edge BNF
        # <edge>   :: <spline> (';' <spline>)*
        #   <spline> :: <endp>? <startp>? <point> <triple>+
        #   <point>  :: <x> ',' <y>
        #   <triple> :: <point> <point> <point>
        #   <endp>   :: "e" "," <x> "," <y>
        ##        spline ( ';' spline )*
        ##where spline  =   (endp)? (startp)? point (triple)+
        ##and triple    =   point point point
        ##and endp  =   "e,%d,%d"
        ##and startp    =   "s,%d,%d"
        ##If a spline has points p1 p2 p3 ... pn, (n = 1 (mod 3)), the points correspond to the control points of a B-spline from p1 to pn. If startp is given, it touches one node of the edge, and the arrowhead goes from p1 to startp. If startp is not given, p1 touches a node. Similarly for pn and endp.
        pos = edge.attr.get('pos')
        if pos:
            segments = pos.split(';')
        else:
            return []

        return_segments = []
        for pos in segments:
            points = pos.split(' ')
            # check direction
            arrow_style = '--'
            i = 0
            if points[i].startswith('s'):
                p = points[0].split(',')
                tmp = "%s,%s" % (p[1], p[2])
                if points[1].startswith('e'):
                    points[2] = tmp
                else:
                    points[1] = tmp
                del points[0]
                arrow_style = '<-'
                i += 1
            if points[0].startswith('e'):
                p = points[0].split(',')
                points.pop()
                points.append("%s,%s" % (p[1], p[2]))
                del points[0]
                arrow_style = '->'
                i += 1
            if i > 1:
                arrow_style = '<->'

            arrow_style = self.get_output_arrow_styles(arrow_style, edge)

            return_segments.append((arrow_style, points))

        return return_segments

    def do_edges(self):
        s = ""
        s += self.set_color(('cC', "black"))
        for edge in self.edges:
            general_draw_string = edge.attr.get('_draw_', "")
            label_string = edge.attr.get('_ldraw_', "")
            head_arrow_string = edge.attr.get('_hdraw_', "")
            tail_arrow_string = edge.attr.get('_tdraw_', "")
            tail_label_string = edge.attr.get('_tldraw_', "")
            head_label_string = edge.attr.get('_hldraw_', "")

            # Note that the order of the draw strings should be the same
            # as in the xdot output.
            drawstring = general_draw_string + " " + head_arrow_string + " " + tail_arrow_string \
                         + " " + label_string
            drawop, stat = parse_drawstring(drawstring)
            if not drawstring.strip():
                continue
            s += self.output_edge_comment(edge)
            if self.options.get('duplicate'):
                s += self.start_edge()
                s += self.do_draw_op(drawop, edge, stat)
                s += self.do_drawstring(tail_label_string, edge, "tailtexlbl")
                s += self.do_drawstring(head_label_string, edge, "headtexlbl")
                s += self.end_edge()
            else:
                s += self.draw_edge(edge)
                s += self.do_drawstring(label_string, edge)
                s += self.do_drawstring(tail_label_string, edge, "tailtexlbl")
                s += self.do_drawstring(head_label_string, edge, "headtexlbl")
        self.body += s

    def do_graph(self):
        general_draw_string = self.graph.attr.get('_draw_', "")
        label_string = self.graph.attr.get('_ldraw_', "")
        # Avoid filling background of graphs with white
        if general_draw_string.startswith('c 5 -white C 5 -white') \
                and not self.graph.attr.get('style'):
            general_draw_string = ''
        if getattr(self.graph, '_draw_', None):
            # bug
            general_draw_string = "c 5 -black " + general_draw_string  # self.graph._draw_
            pass
        drawstring = general_draw_string + " " + label_string
        if drawstring.strip():
            s = self.start_graph(self.graph)
            g = self.do_drawstring(drawstring, self.graph)
            e = self.end_graph(self.graph)
            if g.strip():
                self.body += s + g + e

    def set_options(self):
        # process options
        # Warning! If graph attribute is true and command line option is false,
        # the graph attribute will be used. Command line option should have
        # precedence.
        self.options['alignstr'] = self.options.get('alignstr', '') \
                                   or getattr(self.main_graph, 'd2talignstr', '')

        # Todo: bad!
        self.options['valignmode'] = getattr(self.main_graph, 'd2tvalignmode', '') \
                                     or self.options.get('valignmode', 'center')

    def convert(self, dotdata):
        # parse data processed by dot.
        log.debug('Start conversion')
        main_graph = parse_dot_data(dotdata)

        if not self.dopreproc and not hasattr(main_graph, 'xdotversion'):
            # Older versions of Graphviz does not include the xdotversion
            # attribute
            if not (dotdata.find('_draw_') > 0 or dotdata.find('_ldraw_') > 0):
                # need to convert to xdot format
                # Warning. Pydot will not include custom attributes
                log.info('Trying to create xdotdata')

                tmpdata = create_xdot(dotdata, self.options.get('prog', 'dot'),
                                      options=self.options.get('progoptions', ''))
                if tmpdata is None or not tmpdata.strip():
                    log.error('Failed to create xdotdata. Is Graphviz installed?')
                    sys.exit(1)
                log.debug('xdotdata:\n' + str(tmpdata))
                main_graph = parse_dot_data(tmpdata)
                log.debug('dotparsing graph:\n' + str(main_graph))
            else:
                # old version
                pass

        self.main_graph = main_graph
        self.pencolor = ""
        self.fillcolor = ""
        self.linewidth = 1
        # Detect graph type
        self.directedgraph = main_graph.directed

        if self.dopreproc:
            return self.do_preview_preproc()

        # Remove annoying square
        # Todo: Remove squares from subgraphs. See pgram.dot
        dstring = self.main_graph.attr.get('_draw_', "")
        if dstring:
            self.main_graph.attr['_draw_'] = ""

        self.set_options()

        # A graph can consists of nested graph. Extract all graphs
        graphlist = get_graphlist(self.main_graph, [])

        self.body += self.start_fig()

        # To get correct drawing order we need to iterate over the graphs
        # multiple times. First we draw the graph graphics, then nodes and
        # finally the edges.

        # todo: support the outputorder attribute
        for graph in graphlist:
            self.graph = graph
            self.do_graph()

        if True:
            self.nodes = list(main_graph.allnodes)
            self.edges = list(main_graph.alledges)
            if not self.options.get('switchdraworder'):
                self.do_edges()  # tmp
                self.do_nodes()
            else:
                self.do_nodes()
                self.do_edges()

        self.body += self.end_fig()
        return self.output()

    def clean_template(self, template):
        """Remove preprocsection or outputsection"""
        if not self.dopreproc and self.options.get('codeonly'):
            r = re.compile('<<startcodeonlysection>>(.*?)<<endcodeonlysection>>',
                           re.DOTALL | re.MULTILINE)
            m = r.search(template)
            if m:
                return m.group(1).strip()
        if not self.dopreproc and self.options.get('figonly'):
            r = re.compile('<<start_figonlysection>>(.*?)<<end_figonlysection>>',
                           re.DOTALL | re.MULTILINE)
            m = r.search(template)
            if m:
                return m.group(1)
            r = re.compile('<<startfigonlysection>>(.*?)<<endfigonlysection>>',
                           re.DOTALL | re.MULTILINE)
            m = r.search(template)
            if m:
                return m.group(1)

        if self.dopreproc:
            r = re.compile('<<startoutputsection>>.*?<<endoutputsection>>',
                           re.DOTALL | re.MULTILINE)
        else:
            r = re.compile('<<startpreprocsection>>.*?<<endpreprocsection>>',
                           re.DOTALL | re.MULTILINE)
            # remove codeonly and figonly section
        r2 = re.compile('<<start_figonlysection>>.*?<<end_figonlysection>>',
                        re.DOTALL | re.MULTILINE)
        tmp = r2.sub('', template)
        r2 = re.compile('<<startcodeonlysection>>.*?<<endcodeonlysection>>',
                        re.DOTALL | re.MULTILINE)
        tmp = r2.sub('', tmp)
        return r.sub('', tmp)

    def init_template_vars(self):
        variables = {}
        # get bounding box
        bbstr = self.main_graph.attr.get('bb', '')
        if bbstr:
            bb = bbstr.split(',')
            variables['<<bbox>>'] = "(%sbp,%sbp)(%sbp,%sbp)\n" % (
                smart_float(bb[0]), smart_float(bb[1]), smart_float(bb[2]), smart_float(bb[3]))
            variables['<<bbox.x0>>'] = bb[0]
            variables['<<bbox.y0>>'] = bb[1]
            variables['<<bbox.x1>>'] = bb[2]
            variables['<<bbox.y1>>'] = bb[3]
        variables['<<figcode>>'] = self.body.strip()
        variables['<<drawcommands>>'] = self.body.strip()
        variables['<<textencoding>>'] = self.textencoding
        docpreamble = (self.options.get('docpreamble', '')
                       or getattr(self.main_graph, 'd2tdocpreamble', ''))
        variables['<<docpreamble>>'] = docpreamble
        variables['<<figpreamble>>'] = self.options.get('figpreamble', '') \
                                       or getattr(self.main_graph, 'd2tfigpreamble', '%')
        variables['<<figpostamble>>'] = self.options.get('figpostamble', '') \
                                        or getattr(self.main_graph, 'd2tfigpostamble', '')
        variables['<<graphstyle>>'] = self.options.get('graphstyle', '') \
                                      or getattr(self.main_graph, 'd2tgraphstyle', '')
        variables['<<margin>>'] = self.options.get('margin', '0pt')
        variables['<<startpreprocsection>>'] = variables['<<endpreprocsection>>'] = ''
        variables['<<startoutputsection>>'] = variables['<<endoutputsection>>'] = ''
        if self.options.get('gvcols'):
            variables['<<gvcols>>'] = "\\input{gvcols.tex}"
        else:
            variables['<<gvcols>>'] = ""
        self.templatevars = variables

    def output(self):
        self.init_template_vars()
        template = self.clean_template(self.template)
        code = replace_tags(template, self.templatevars,
                            self.templatevars)
        return code

    def get_label(self, drawobj, label_attribute="label", tex_label_attribute="texlbl"):
        text = ""
        texmode = self.options.get('texmode', 'verbatim')
        if getattr(drawobj, 'texmode', ''):
            texmode = drawobj.texmode
        text = getattr(drawobj, label_attribute, None)

        # log.warning('text %s %s',text,str(drawobj))

        if text is None or text.strip() == '\\N':
            if not isinstance(drawobj, dotparsing.DotEdge):
                text = getattr(drawobj, 'name', None) or \
                       getattr(drawobj, 'graph_name', '')
                text = text.replace("\\\\", "\\")
            else:
                text = ''
        elif text.strip() == '\\N':
            text = ''
        else:
            text = text.replace("\\\\", "\\")

        if getattr(drawobj, tex_label_attribute, ''):
            # the texlbl overrides everything
            text = drawobj.texlbl
        elif texmode == 'verbatim':
            # verbatim mode
            text = escape_texchars(text)
            pass
        elif texmode == 'math':
            # math mode
            text = "$%s$" % text

        return text

    def get_node_preproc_code(self, node):
        return node.attr.get('texlbl', '')

    def get_edge_preproc_code(self, edge, attribute="texlbl"):
        return edge.attr.get(attribute, '')

    def get_graph_preproc_code(self, graph):
        return graph.attr.get('texlbl', '')

    def get_margins(self, element):
        """Return element margins"""
        margins = element.attr.get('margin')

        if margins:
            margins = margins.split(',')
            if len(margins) == 1:
                xmargin = ymargin = float(margins[0])
            else:
                xmargin = float(margins[0])
                ymargin = float(margins[1])
        else:
            # use default values
            if isinstance(element, dotparsing.DotEdge):
                xmargin = DEFAULT_EDGELABEL_XMARGIN
                ymargin = DEFAULT_EDGELABEL_YMARGIN
            else:
                xmargin = DEFAULT_LABEL_XMARGIN
                ymargin = DEFAULT_LABEL_YMARGIN
        return xmargin, ymargin

    # Todo: Add support for head and tail labels!
    # Todo: Support rect nodes if possible.
    def do_preview_preproc(self):
        # setDotAttr(self.maingraph)
        self.init_template_vars()
        template = self.clean_template(self.template)
        template = replace_tags(template, self.templatevars,
                                self.templatevars)
        pp = TeXDimProc(template, self.options)
        usednodes = {}
        usededges = {}
        usedgraphs = {}

        # iterate over every element in the graph
        counter = 0
        for node in self.main_graph.allnodes:
            name = node.name
            if node.attr.get('fixedsize', '') == 'true' \
                    or node.attr.get('style', '') in ['invis', 'invisible']:
                continue
            if node.attr.get('shape', '') == 'record':
                log.warning('Record nodes not supported in preprocessing mode: %s', name)
                continue
            texlbl = self.get_label(node)

            if texlbl:
                node.attr['texlbl'] = texlbl
                code = self.get_node_preproc_code(node)
                pp.add_snippet(name, code)

            usednodes[name] = node

        for edge in dotparsing.flatten(self.main_graph.alledges):
            if not edge.attr.get('label') and not edge.attr.get('texlbl') and not edge.attr.get("headlabel") \
                    and not edge.attr.get("taillabel"):
                continue
            # Ensure that the edge name is unique.
            name = edge.src.name + edge.dst.name + str(counter)
            if is_multiline_label(edge):
                continue
            label = self.get_label(edge)
            headlabel = self.get_label(edge, "headlabel", "headtexlbl")
            taillabel = self.get_label(edge, "taillabel", "tailtexlbl")
            if label:
                name = edge.src.name + edge.dst.name + str(counter)
                edge.attr['texlbl'] = label
                code = self.get_edge_preproc_code(edge)
                pp.add_snippet(name, code)

            if headlabel:
                headlabel_name = name + "headlabel"
                edge.attr['headtexlbl'] = headlabel
                code = self.get_edge_preproc_code(edge, "headtexlbl")
                pp.add_snippet(headlabel_name, code)

            if taillabel:
                taillabel_name = name + "taillabel"
                edge.attr['tailtexlbl'] = taillabel
                code = self.get_edge_preproc_code(edge, "tailtexlbl")
                pp.add_snippet(taillabel_name, code)

            counter += 1
            usededges[name] = edge

        for graph in self.main_graph.allgraphs:
            if not graph.attr.get('label') and not graph.attr.get('texlbl'):
                continue
            # Make sure that the name is unique
            name = graph.name + str(counter)

            counter += 1
            label = self.get_label(graph)
            graph.attr['texlbl'] = label
            code = self.get_graph_preproc_code(graph)
            pp.add_snippet(name, code)
            usedgraphs[name] = graph

        ok = pp.process()

        if not ok:
            errormsg = """\
Failed to preprocess the graph.
Is the preview LaTeX package installed? ((Debian package preview-latex-style)
To see what happened, run dot2tex with the --debug option.
"""
            log.error(errormsg)
            sys.exit(1)

        for name, item in usednodes.items():
            if not item.attr.get('texlbl'):
                continue
            node = item
            hp, dp, wt = pp.texdims[name]
            if self.options.get('rawdim'):
                # use dimensions from preview.sty directly
                node.attr['width'] = wt
                node.attr['height'] = hp + dp
                node.attr['label'] = " "
                node.attr['fixedsize'] = 'true'
                self.main_graph.allitems.append(node)
                continue

            xmargin, ymargin = self.get_margins(node)
            ht = hp + dp
            minwidth = float(item.attr.get('width') or DEFAULT_NODE_WIDTH)
            minheight = float(item.attr.get('height') or DEFAULT_NODE_HEIGHT)
            if self.options.get('nominsize'):
                width = wt + 2 * xmargin
                height = ht + 2 * ymargin
            else:
                if (wt + 2 * xmargin) < minwidth:
                    width = minwidth
                else:
                    width = wt + 2 * xmargin
                height = ht
                if ((hp + dp) + 2 * ymargin) < minheight:
                    height = minheight
                else:
                    height = ht + 2 * ymargin
            # Treat shapes with equal width and height differently
            # Warning! Rectangles will not always fit inside a circle
            #          Should use the diagonal.
            if item.attr.get('shape', '') in ['circle', 'Msquare', 'doublecircle', 'Mcircle']:
                if wt < height and width < height:
                    width = height
                else:
                    height = width

            node.attr['width'] = width
            node.attr['height'] = height
            node.attr['label'] = " "

            node.attr['fixedsize'] = 'true'
            self.main_graph.allitems.append(node)

        for name, item in usededges.items():
            edge = item
            hp, dp, wt = pp.texdims[name]
            xmargin, ymargin = self.get_margins(edge)
            labelcode = '<<<table border="0" cellborder="0" cellpadding="0">' \
                        '<tr><td fixedsize="true" width="%s" height="%s">a</td>' \
                        '</tr></table>>>'
            if "texlbl" in edge.attr:
                edge.attr['label'] = labelcode % ((wt + 2 * xmargin) * 72, (hp + dp + 2 * ymargin) * 72)
            if "tailtexlbl" in edge.attr:
                hp, dp, wt = pp.texdims[name + "taillabel"]
                edge.attr['taillabel'] = labelcode % ((wt + 2 * xmargin) * 72, (hp + dp + 2 * ymargin) * 72)
            if "headtexlbl" in edge.attr:
                hp, dp, wt = pp.texdims[name + "headlabel"]
                edge.attr['headlabel'] = labelcode % ((wt + 2 * xmargin) * 72, (hp + dp + 2 * ymargin) * 72)

        for name, item in usedgraphs.items():
            graph = item
            hp, dp, wt = pp.texdims[name]
            xmargin, ymargin = self.get_margins(graph)
            labelcode = '<<<table border="0" cellborder="0" cellpadding="0">' \
                        '<tr><td fixedsize="true" width="%s" height="%s">a</td>' \
                        '</tr></table>>>'
            graph.attr['label'] = labelcode % ((wt + 2 * xmargin) * 72, (hp + dp + 2 * ymargin) * 72)

        self.main_graph.attr['d2toutputformat'] = self.options.get('format',
                                                                   DEFAULT_OUTPUT_FORMAT)
        graphcode = str(self.main_graph)
        graphcode = graphcode.replace('<<<', '<<')
        graphcode = graphcode.replace('>>>', '>>')
        return graphcode

    def get_output_arrow_styles(self, arrow_style, edge):
        return arrow_style


PSTRICKS_TEMPLATE = r"""\documentclass{article}
% <<bbox>>
\usepackage[x11names,svgnames]{xcolor}
\usepackage[<<textencoding>>]{inputenc}
\usepackage{graphicx}
\usepackage{pstricks}
\usepackage{amsmath}
<<startpreprocsection>>%
\usepackage[active,auctex]{preview}
<<endpreprocsection>>%
<<gvcols>>%
<<docpreamble>>%


\begin{document}
\pagestyle{empty}
<<startpreprocsection>>%
<<preproccode>>%
<<endpreprocsection>>%
<<startoutputsection>>%
\enlargethispage{100cm}

% Start of code
\begin{pspicture}[linewidth=1bp<<graphstyle>>]<<bbox>>
  \pstVerb{2 setlinejoin} % set line join style to 'mitre'
<<figpreamble>>%
<<drawcommands>>
<<figpostamble>>%
\end{pspicture}
% End of code
<<endoutputsection>>%
\end{document}
%
<<start_figonlysection>>
\begin{pspicture}[linewidth=1bp<<graphstyle>>]<<bbox>>
  \pstVerb{2 setlinejoin} % set line join style to 'mitre'
<<figpreamble>>%
<<drawcommands>>
<<figpostamble>>%
\end{pspicture}
<<end_figonlysection>>
%
<<startcodeonlysection>>
<<figpreamble>>%
<<drawcommands>>
<<figpostamble>>%
<<endcodeonlysection>>
"""


class Dot2PSTricksConv(DotConvBase):
    """PSTricks converter backend"""

    def __init__(self, options=None):
        DotConvBase.__init__(self, options)
        if not self.template:
            self.template = PSTRICKS_TEMPLATE
        self.styles = dict(
            dotted="linestyle=dotted",
            dashed="linestyle=dashed",
            bold="linewidth=2pt",
            solid="",
            filled="",
        )

    def do_graphtmp(self):
        self.pencolor = ""
        self.fillcolor = ""
        self.color = ""
        self.body += '{\n'
        DotConvBase.do_graph(self)
        self.body += '}\n'

    def draw_ellipse(self, drawop, style=None):
        op, x, y, w, h = drawop
        s = ""
        if op == 'E':
            if style:
                style = style.replace('filled', '')
            stylestr = 'fillstyle=solid'
        else:
            stylestr = ""

        if style:
            if stylestr:
                stylestr += ',' + style
            else:
                stylestr = style

        s += "  \\psellipse[%s](%sbp,%sbp)(%sbp,%sbp)\n" % (stylestr, smart_float(x), smart_float(y),
                                                           # w+self.linewidth,h+self.linewidth)
                                                           smart_float(w), smart_float(h))

        return s

    def draw_polygon(self, drawop, style=None):
        op, points = drawop
        pp = ['(%sbp,%sbp)' % (smart_float(p[0]), smart_float(p[1])) for p in points]
        stylestr = ""
        if op == 'P':
            if style:
                style = style.replace('filled', '')
            stylestr = "fillstyle=solid"
        if style:
            if stylestr:
                stylestr += ',' + style
            else:
                stylestr = style

        s = "  \\pspolygon[%s]%s\n" % (stylestr, "".join(pp))
        return s

    def draw_polyline(self, drawop, style=None):
        op, points = drawop
        pp = ['(%sbp,%sbp)' % (smart_float(p[0]), smart_float(p[1])) for p in points]
        s = "  \\psline%s\n" % "".join(pp)
        return s

    def draw_bezier(self, drawop, style=None):
        op, points = drawop
        pp = []
        for point in points:
            pp.append("(%sbp,%sbp)" % (smart_float(point[0]), smart_float(point[1])))

        arrowstyle = ""
        return "  \\psbezier{%s}%s\n" % (arrowstyle, "".join(pp))

    def draw_text(self, drawop, style=None):
        if len(drawop) == 7:
            c, x, y, align, w, text, valign = drawop
        else:
            c, x, y, align, w, text = drawop
            valign = ""
        if align == "-1":
            alignstr = 'l'  # left aligned
        elif align == "1":
            alignstr = 'r'  # right aligned
        else:
            alignstr = ""  # centered (default)
        if alignstr or valign:
            alignstr = '[' + alignstr + valign + ']'
        s = "  \\rput%s(%sbp,%sbp){%s}\n" % (alignstr, smart_float(x), smart_float(y), text)
        return s

    def set_color(self, drawop):
        c, color = drawop
        color = self.convert_color(color)
        s = ""
        if c == 'c':
            # set pen color
            if self.pencolor != color:
                self.pencolor = color
                s = "  \\psset{linecolor=%s}\n" % color
            else:
                return ""
        elif c == 'C':
            # set fill color
            if self.fillcolor != color:
                self.fillcolor = color
                s = "  \\psset{fillcolor=%s}\n" % color
            else:
                return ""
        elif c == 'cC':
            if self.color != color:
                self.color = color
                self.pencolor = self.fillcolor = color
                s = "  \\psset{linecolor=%s}\n" % color
        else:
            log.warning('Unhandled color: %s', drawop)
        return s

    def set_style(self, drawop):
        c, style = drawop
        psstyle = self.styles.get(style, "")
        if psstyle:
            return "  \\psset{%s}\n" % psstyle
        else:
            return ""

    def filter_styles(self, style):
        filtered_styles = []
        for item in style.split(','):
            keyval = item.strip()
            if keyval.find('setlinewidth') < 0:
                filtered_styles.append(keyval)
        return ', '.join(filtered_styles)

    def start_node(self, node):
        self.pencolor = ""
        self.fillcolor = ""
        self.color = ""
        return "{%\n"

    def end_node(self, node):
        return "}%\n"

    def start_edge(self):
        self.pencolor = ""
        self.fillcolor = ""
        return "{%\n"

    def end_edge(self):
        return "}%\n"

    def start_graph(self, graph):
        self.pencolor = ""
        self.fillcolor = ""
        self.color = ""
        return "{\n"

    def end_graph(self, node):
        return "}\n"

    def draw_edge(self, edge):
        s = ""
        if edge.attr.get('style', '') in ['invis', 'invisible']:
            return ""
        edges = self.get_edge_points(edge)
        for arrowstyle, points in edges:
            if arrowstyle == '--':
                arrowstyle = ''
            color = getattr(edge, 'color', '')
            if self.color != color:
                if color:
                    s += self.set_color(('c', color))
                else:
                    # reset to default color
                    s += self.set_color(('c', 'black'))
            pp = []
            for point in points:
                p = point.split(',')
                pp.append("(%sbp,%sbp)" % (smart_float(p[0]), smart_float(p[1])))

            edgestyle = edge.attr.get('style', '')
            styles = []
            if arrowstyle:
                styles.append('arrows=%s' % arrowstyle)
            if edgestyle:
                edgestyles = [self.styles.get(key.strip(), key.strip())
                              for key in edgestyle.split(',') if key]
                styles.extend(edgestyles)
            if styles:
                stylestr = ",".join(styles)
            else:
                stylestr = ""
            if not self.options.get('straightedges'):
                s += "  \\psbezier[%s]%s\n" % (stylestr, "".join(pp))
            else:
                s += "  \\psline[%s]%s%s\n" % (stylestr, pp[0], pp[-1])
                # s += "  \psbezier[%s]{%s}%s\n" % (stylestr, arrowstyle,"".join(pp))
                ##        if edge.label:
                ##            x,y = edge.lp.split(',')
                ##            #s += "\\rput(%s,%s){%s}\n" % (x,y,edge.label)
        return s

    def init_template_vars(self):
        DotConvBase.init_template_vars(self)
        # Put a ',' before <<graphstyle>>
        graphstyle = self.templatevars.get('<<graphstyle>>', '')
        if graphstyle:
            graphstyle = graphstyle.strip()
            if not graphstyle.startswith(','):
                graphstyle = ',' + graphstyle
                self.templatevars['<<graphstyle>>'] = graphstyle


PGF_TEMPLATE = r"""\documentclass{article}
\usepackage[x11names, svgnames, rgb]{xcolor}
\usepackage[<<textencoding>>]{inputenc}
\usepackage{tikz}
\usetikzlibrary{snakes,arrows,shapes}
\usepackage{amsmath}
<<startpreprocsection>>%
\usepackage[active,auctex]{preview}
<<endpreprocsection>>%
<<gvcols>>%
<<startoutputsection>>
<<cropcode>>%
<<endoutputsection>>
<<docpreamble>>%

\begin{document}
\pagestyle{empty}
%
<<startpreprocsection>>%
<<preproccode>>
<<endpreprocsection>>%
%
<<startoutputsection>>
\enlargethispage{100cm}
% Start of code
% \begin{tikzpicture}[anchor=mid,>=latex',line join=bevel,<<graphstyle>>]
\begin{tikzpicture}[>=latex',line join=bevel,<<graphstyle>>]
  \pgfsetlinewidth{1bp}
<<figpreamble>>%
<<drawcommands>>
<<figpostamble>>%
\end{tikzpicture}
% End of code
<<endoutputsection>>
%
\end{document}
%
<<start_figonlysection>>
\begin{tikzpicture}[>=latex,line join=bevel,<<graphstyle>>]
  \pgfsetlinewidth{1bp}
<<figpreamble>>%
<<drawcommands>>
<<figpostamble>>%
\end{tikzpicture}
<<end_figonlysection>>
<<startcodeonlysection>>
<<figpreamble>>%
<<drawcommands>>
<<figpostamble>>%
<<endcodeonlysection>>
"""

PGF210_TEMPLATE = r"""\documentclass{article}
% dot2tex template for PGF 2.10
\usepackage[x11names, svgnames, rgb]{xcolor}
\usepackage[<<textencoding>>]{inputenc}
\usepackage{tikz}
\usetikzlibrary{snakes,arrows,shapes}
\usepackage{amsmath}
<<startpreprocsection>>%
\usepackage[active,auctex]{preview}
<<endpreprocsection>>%
<<gvcols>>%
<<startoutputsection>>
<<cropcode>>%
<<endoutputsection>>
<<docpreamble>>%

\begin{document}
\pagestyle{empty}
%
<<startpreprocsection>>%
<<preproccode>>
<<endpreprocsection>>%
%
<<startoutputsection>>
\enlargethispage{100cm}
% Start of code
% \begin{tikzpicture}[anchor=mid,>=latex',line join=bevel,<<graphstyle>>]
\begin{tikzpicture}[>=latex',line join=bevel,<<graphstyle>>]
  \pgfsetlinewidth{1bp}
<<figpreamble>>%
<<drawcommands>>
<<figpostamble>>%
\end{tikzpicture}
% End of code
<<endoutputsection>>
%
\end{document}
%
<<start_figonlysection>>
\begin{tikzpicture}[>=latex,line join=bevel,<<graphstyle>>]
  \pgfsetlinewidth{1bp}
<<figpreamble>>%
<<drawcommands>>
<<figpostamble>>%
\end{tikzpicture}
<<end_figonlysection>>
<<startcodeonlysection>>
<<figpreamble>>%
<<drawcommands>>
<<figpostamble>>%
<<endcodeonlysection>>
"""

PGF118_TEMPLATE = r"""\documentclass{article}
\usepackage[x11names, rgb]{xcolor}
\usepackage[<<textencoding>>]{inputenc}
\usepackage{tikz}
\usepackage{pgflibrarysnakes}
\usepackage{pgflibraryarrows,pgflibraryshapes}
\usepackage{amsmath}
<<startpreprocsection>>%
\usepackage[active,auctex]{preview}
<<endpreprocsection>>%
<<gvcols>>%
<<cropcode>>%
<<docpreamble>>%

\begin{document}
\pagestyle{empty}
%
<<startpreprocsection>>%
<<preproccode>>
<<endpreprocsection>>%
%
<<startoutputsection>>
\enlargethispage{100cm}
% Start of code
\begin{tikzpicture}[>=latex',join=bevel,<<graphstyle>>]
  \pgfsetlinewidth{1bp}
<<figpreamble>>%
<<drawcommands>>
<<figpostamble>>%
\end{tikzpicture}
% End of code
<<endoutputsection>>
%
\end{document}
%
<<start_figonlysection>>
\begin{tikzpicture}[>=latex,join=bevel,<<graphstyle>>]
  \pgfsetlinewidth{1bp}
<<figpreamble>>%
<<drawcommands>>
<<figpostamble>>%
\end{tikzpicture}
<<end_figonlysection>>
<<startcodeonlysection>>
<<figpreamble>>%
<<drawcommands>>
<<figpostamble>>%
<<endcodeonlysection>>
"""


class Dot2PGFConv(DotConvBase):
    """PGF/TikZ converter backend"""
    arrows_map_210 = {"dot": "*", "odot": "o", "empty": "open triangle 45", "invempty": "open triangle 45 reversed",
                      "diamond": "diamond", "odiamond": "open diamond", "ediamond": "open diamond", "box": "square",
                      "obox": "open square", "vee": "stealth'", "open": "stealth'", "tee": "|",
                      "crow": "stealth reversed"}

    def __init__(self, options=None):
        DotConvBase.__init__(self, options)
        if not self.template:
            if options.get('pgf118'):
                self.template = PGF118_TEMPLATE
            elif options.get('pgf210'):
                self.template = PGF210_TEMPLATE
            else:
                self.template = PGF_TEMPLATE
        self.styles = dict(dashed='dashed', dotted='dotted',
                           bold='very thick', filled='fill', invis="",
                           rounded='rounded corners', )
        self.dashstyles = dict(
            dashed='\\pgfsetdash{{3pt}{3pt}}{0pt}',
            dotted='\\pgfsetdash{{\\pgflinewidth}{2pt}}{0pt}',
            bold='\\pgfsetlinewidth{1.2pt}')

    def start_node(self, node):
        # Todo: Should find a more elegant solution
        self.pencolor = ""
        self.fillcolor = ""
        self.color = ""
        return "\\begin{scope}\n"

    def end_node(self, node):
        return "\\end{scope}\n"

    def start_edge(self):
        # Todo: Should find a more elegant solution
        # self.pencolor = "";
        # self.fillcolor = ""
        # self.color = ""
        return "\\begin{scope}\n"

    def end_edge(self):
        return "\\end{scope}\n"

    def start_graph(self, graph):
        # Todo: Should find a more elegant solution
        self.pencolor = ""
        self.fillcolor = ""
        self.color = ""
        return "\\begin{scope}\n"

    def end_graph(self, graph):
        return "\\end{scope}\n"

    def set_color(self, drawop):
        c, color = drawop
        res = self.convert_color(color, True)
        opacity = None
        if len(res) == 2:
            ccolor, opacity = res
        else:
            ccolor = res
        s = ""
        if c == 'cC':
            if self.color != color:
                self.color = color
                self.pencolor = color
                self.fillcolor = color
                if ccolor.startswith('{'):
                    # rgb or hsb
                    s += "  \\definecolor{newcol}%s;\n" % ccolor
                    ccolor = 'newcol'
                s += "  \\pgfsetcolor{%s}\n" % ccolor
        elif c == 'c':
            # set pen color
            if self.pencolor != color:
                self.pencolor = color
                self.color = ''
                if ccolor.startswith('{'):
                    # rgb or hsb
                    s += "  \\definecolor{strokecol}%s;\n" % ccolor
                    ccolor = 'strokecol'
                s += "  \\pgfsetstrokecolor{%s}\n" % ccolor
            else:
                return ""
        elif c == 'C':
            # set fill color
            if self.fillcolor != color:
                self.fillcolor = color
                self.color = ''
                if ccolor.startswith('{'):
                    # rgb
                    s += "  \\definecolor{fillcol}%s;\n" % ccolor
                    ccolor = 'fillcol'
                s += "  \\pgfsetfillcolor{%s}\n" % ccolor
                if not opacity is None:
                    self.opacity = opacity
                    # Todo: The opacity should probably be set directly when drawing
                    # The \pgfsetfillcopacity cmd affects text as well
                    # s += "  \pgfsetfillopacity{%s};\n" % opacity
                else:
                    self.opacity = None
            else:
                return ""
        return s

    def set_style(self, drawop):
        c, style = drawop
        pgfstyle = self.dashstyles.get(style, "")
        if pgfstyle:
            return "  %s\n" % pgfstyle
        else:
            return ""

    def filter_styles(self, style):
        filtered_styles = []
        for item in style.split(','):
            keyval = item.strip()
            if keyval.find('setlinewidth') < 0 and not keyval == 'filled':
                filtered_styles.append(keyval)
        return ', '.join(filtered_styles)

    def draw_ellipse(self, drawop, style=None):
        op, x, y, w, h = drawop
        s = ""
        if op == 'E':
            if self.opacity is not None:
                # Todo: Need to know the state of the current node
                cmd = 'filldraw [opacity=%s]' % self.opacity
            else:
                cmd = 'filldraw'
        else:
            cmd = "draw"

        if style:
            stylestr = " [%s]" % style
        else:
            stylestr = ''
        s += "  \\%s%s (%sbp,%sbp) ellipse (%sbp and %sbp);\n" % (cmd, stylestr, smart_float(x), smart_float(y),
                                                                 # w+self.linewidth,h+self.linewidth)
                                                                 smart_float(w), smart_float(h))
        return s

    def draw_polygon(self, drawop, style=None):
        op, points = drawop
        pp = ['(%sbp,%sbp)' % (smart_float(p[0]), smart_float(p[1])) for p in points]
        cmd = "draw"
        if op == 'P':
            cmd = "filldraw"

        if style:
            stylestr = " [%s]" % style
        else:
            stylestr = ''
        s = "  \\%s%s %s -- cycle;\n" % (cmd, stylestr, " -- ".join(pp))
        return s

    def draw_polyline(self, drawop, style=None):
        op, points = drawop
        pp = ['(%sbp,%sbp)' % (smart_float(p[0]), smart_float(p[1])) for p in points]
        stylestr = ''
        return "  \\draw%s %s;\n" % (stylestr, " -- ".join(pp))

    def draw_text(self, drawop, style=None):
        # The coordinates given by drawop are not the same as the node
        # coordinates! This may give som odd results if graphviz' and
        # LaTeX' fonts are very different.
        if len(drawop) == 7:
            c, x, y, align, w, text, valign = drawop
        else:
            c, x, y, align, w, text = drawop

        styles = []
        if align == "-1":
            alignstr = 'right'  # left aligned
        elif align == "1":
            alignstr = 'left'  # right aligned
        else:
            alignstr = ""  # centered (default)
        styles.append(alignstr)
        styles.append(style)
        lblstyle = ",".join([i for i in styles if i])
        if lblstyle:
            lblstyle = '[' + lblstyle + ']'
        s = "  \\draw (%sbp,%sbp) node%s {%s};\n" % (smart_float(x), smart_float(y), lblstyle, text)
        return s

    def draw_bezier(self, drawop, style=None):
        s = ""
        c, points = drawop
        pp = []
        for point in points:
            pp.append("(%sbp,%sbp)" % (smart_float(point[0]), smart_float(point[1])))

        pstrs = ["%s .. controls %s and %s " % p for p in nsplit(pp, 3)]
        stylestr = ''
        s += "  \\draw%s %s .. %s;\n" % (stylestr, " .. ".join(pstrs), pp[-1])
        return s

    def do_edges(self):
        s = ""
        s += self.set_color(('cC', "black"))
        for edge in self.edges:
            general_draw_string = getattr(edge, '_draw_', "")
            label_string = getattr(edge, '_ldraw_', "")
            head_arrow_string = getattr(edge, '_hdraw_', "")
            tail_arrow_string = getattr(edge, '_tdraw_', "")
            tail_label_string = getattr(edge, '_tldraw_', "")
            head_label_string = getattr(edge, '_hldraw_', "")

            # Note that the order of the draw strings should be the same
            # as in the xdot output.
            drawstring = general_draw_string + " " + head_arrow_string + " " + tail_arrow_string \
                         + " " + label_string
            draw_operations, stat = parse_drawstring(drawstring)
            if not drawstring.strip():
                continue
            s += self.output_edge_comment(edge)
            if self.options.get('duplicate'):
                s += self.start_edge()
                s += self.do_draw_op(draw_operations, edge, stat)
                s += self.do_drawstring(tail_label_string, edge, "tailtexlbl")
                s += self.do_drawstring(head_label_string, edge, "headtexlbl")
                s += self.end_edge()
            else:
                topath = getattr(edge, 'topath', None)
                s += self.draw_edge(edge)
                if not self.options.get('tikzedgelabels') and not topath:
                    s += self.do_drawstring(label_string, edge)
                    s += self.do_drawstring(tail_label_string, edge, "tailtexlbl")
                    s += self.do_drawstring(head_label_string, edge, "headtexlbl")
                else:
                    s += self.do_drawstring(tail_label_string, edge, "tailtexlbl")
                    s += self.do_drawstring(head_label_string, edge, "headtexlbl")

        self.body += s

    def draw_edge(self, edge):
        s = ""
        if edge.attr.get('style', '') in ['invis', 'invisible']:
            return ""
        edges = self.get_edge_points(edge)
        for arrowstyle, points in edges:
            # arrowstyle, points = self.get_edge_points(edge)
            # PGF uses the fill style when drawing some arrowheads. We have to
            # ensure that the fill color is the same as the pen color.
            color = getattr(edge, 'color', '')

            if self.color != color:
                if color:
                    s += self.set_color(('cC', color))
                else:
                    # reset to default color
                    s += self.set_color(('cC', 'black'))

            pp = []
            for point in points:
                p = point.split(',')
                pp.append("(%sbp,%sbp)" % (smart_float(p[0]), smart_float(p[1])))

            edgestyle = edge.attr.get('style', '')

            styles = []
            if arrowstyle != '--':
                styles = [arrowstyle]

            if edgestyle:
                edgestyles = [self.styles.get(key.strip(), key.strip()) for key in edgestyle.split(',') if key]
                styles.extend(edgestyles)

            stylestr = ",".join(styles)
            topath = getattr(edge, 'topath', None)

            pstrs = ["%s .. controls %s and %s " % x for x in nsplit(pp, 3)]
            extra = ""
            if self.options.get('tikzedgelabels') or topath:
                edgelabel = self.get_label(edge)
                # log.warning('label: %s', edgelabel)
                lblstyle = getattr(edge, 'lblstyle', '')
                if lblstyle:
                    lblstyle = '[' + lblstyle + ']'
                else:
                    lblstyle = ''
                if edgelabel:
                    extra = " node%s {%s}" % (lblstyle, edgelabel)
            src = pp[0]
            dst = pp[-1]
            if topath:
                s += "  \\draw [%s] %s to[%s]%s %s;\n" % (stylestr, src,
                                                         topath, extra, dst)
            elif not self.options.get('straightedges'):
                s += "  \\draw [%s] %s ..%s %s;\n" % (stylestr, " .. ".join(pstrs), extra, pp[-1])
            else:
                s += "  \\draw [%s] %s --%s %s;\n" % (stylestr, pp[0], extra, pp[-1])

        return s

    def get_output_arrow_styles(self, arrow_style, edge):
        dot_arrow_head = edge.attr.get("arrowhead")
        dot_arrow_tail = edge.attr.get("arrowtail")
        output_arrow_style = arrow_style
        if dot_arrow_head:
            pgf_arrow_head = self.arrows_map_210.get(dot_arrow_head)
            if pgf_arrow_head:
                output_arrow_style = output_arrow_style.replace(">", pgf_arrow_head)
        if dot_arrow_tail:
            pgf_arrow_tail = self.arrows_map_210.get(dot_arrow_tail)
            if pgf_arrow_tail:
                output_arrow_style = output_arrow_style.replace("<", pgf_arrow_tail)
        return output_arrow_style

    def init_template_vars(self):
        DotConvBase.init_template_vars(self)
        if self.options.get('crop'):
            cropcode = "\\usepackage[active,tightpage]{preview}\n" + \
                       "\\PreviewEnvironment{tikzpicture}\n" + \
                       "\\setlength\\PreviewBorder{%s}" % self.options.get('margin', '0pt')
        else:
            cropcode = ""
        variables = {'<<cropcode>>': cropcode}
        self.templatevars.update(variables)

    def get_node_preproc_code(self, node):
        lblstyle = node.attr.get('lblstyle', '')
        text = node.attr.get('texlbl', '')
        if lblstyle:
            return "  \\tikz \\node[%s] {%s};\n" % (lblstyle, text)
        else:
            return r"\tikz \node {" + text + "};"

    def get_edge_preproc_code(self, edge, attribute="texlbl"):
        lblstyle = edge.attr.get('lblstyle', '')
        text = edge.attr.get(attribute, '')
        if lblstyle:
            return "  \\tikz \\node[%s] {%s};\n" % (lblstyle, text)
        else:
            return r"\tikz \node " + "{" + text + "};"

    def get_graph_preproc_code(self, graph):
        lblstyle = graph.attr.get('lblstyle', '')
        text = graph.attr.get('texlbl', '')
        if lblstyle:
            return "  \\tikz \\node[%s] {%s};\n" % (lblstyle, text)
        else:
            return r"\tikz \node {" + text + "};"


TIKZ_TEMPLATE = r"""\documentclass{article}
\usepackage[x11names, svgnames, rgb]{xcolor}
\usepackage[<<textencoding>>]{inputenc}
\usepackage{tikz}
\usetikzlibrary{snakes,arrows,shapes}
\usepackage{amsmath}
<<startpreprocsection>>%
\usepackage[active,auctex]{preview}
<<endpreprocsection>>%
<<gvcols>>%
<<startoutputsection>>
<<cropcode>>%
<<endoutputsection>>
<<docpreamble>>%

\begin{document}
\pagestyle{empty}
%
<<startpreprocsection>>%
<<preproccode>>
<<endpreprocsection>>%
%
<<startoutputsection>>
\enlargethispage{100cm}
% Start of code
\begin{tikzpicture}[>=latex',line join=bevel,<<graphstyle>>]
<<figpreamble>>%
<<drawcommands>>
<<figpostamble>>%
\end{tikzpicture}
% End of code
<<endoutputsection>>
%
\end{document}
%
<<start_figonlysection>>
\begin{tikzpicture}[>=latex,line join=bevel,<<graphstyle>>]
<<figpreamble>>%
<<drawcommands>>
<<figpostamble>>%
\end{tikzpicture}
<<end_figonlysection>>
<<startcodeonlysection>>
<<figpreamble>>%
<<drawcommands>>
<<figpostamble>>%
<<endcodeonlysection>>
"""

TIKZ210_TEMPLATE = r"""\documentclass{article}
% dot2tex template for PGF 2.10
\usepackage[x11names, svgnames, rgb]{xcolor}
\usepackage[<<textencoding>>]{inputenc}
\usepackage{tikz}
\usetikzlibrary{snakes,arrows,shapes}
\usepackage{amsmath}
<<startpreprocsection>>%
\usepackage[active,auctex]{preview}
<<endpreprocsection>>%
<<gvcols>>%
<<startoutputsection>>
<<cropcode>>%
<<endoutputsection>>
<<docpreamble>>%

\begin{document}
\pagestyle{empty}
%
<<startpreprocsection>>%
<<preproccode>>
<<endpreprocsection>>%
%
<<startoutputsection>>
\enlargethispage{100cm}
% Start of code
\begin{tikzpicture}[>=latex',line join=bevel,<<graphstyle>>]
<<figpreamble>>%
<<drawcommands>>
<<figpostamble>>%
\end{tikzpicture}
% End of code
<<endoutputsection>>
%
\end{document}
%
<<start_figonlysection>>
\begin{tikzpicture}[>=latex,line join=bevel,<<graphstyle>>]
<<figpreamble>>%
<<drawcommands>>
<<figpostamble>>%
\end{tikzpicture}
<<end_figonlysection>>
<<startcodeonlysection>>
<<figpreamble>>%
<<drawcommands>>
<<figpostamble>>%
<<endcodeonlysection>>
"""

TIKZ118_TEMPLATE = r"""\documentclass{article}
\usepackage[x11names, svgnames, rgb]{xcolor}
\usepackage[<<textencoding>>]{inputenc}
\usepackage{tikz}
\usepackage{pgflibrarysnakes}
\usepackage{pgflibraryarrows,pgflibraryshapes}
\usepackage{amsmath}
<<startpreprocsection>>%
\usepackage[active,auctex]{preview}
<<endpreprocsection>>%
<<gvcols>>%
<<cropcode>>%
<<docpreamble>>%

\begin{document}
\pagestyle{empty}
%
<<startpreprocsection>>%
<<preproccode>>
<<endpreprocsection>>%
%
<<startoutputsection>>
\enlargethispage{100cm}
% Start of code
\begin{tikzpicture}[>=latex',join=bevel,<<graphstyle>>]
<<figpreamble>>%
<<drawcommands>>
<<figpostamble>>%
\end{tikzpicture}
% End of code
<<endoutputsection>>
%
\end{document}
%
<<start_figonlysection>>
\begin{tikzpicture}[>=latex,join=bevel,<<graphstyle>>]
<<figpreamble>>%
<<drawcommands>>
<<figpostamble>>%
\end{tikzpicture}
<<end_figonlysection>>
<<startcodeonlysection>>
<<figpreamble>>%
<<drawcommands>>
<<figpostamble>>%
<<endcodeonlysection>>
"""


class Dot2TikZConv(Dot2PGFConv):
    """A backend that utilizes the node and edge mechanism of PGF/TikZ"""
    shape_map = {'doublecircle': 'circle, double',
                 'box': 'rectangle',
                 'rect': 'rectangle',
                 'none': 'draw=none',
                 'plaintext': 'draw=none',
                 'polygon': 'regular polygon, regular polygon sides=7',
                 'triangle': 'regular polygon, regular polygon sides=3',
                 'square': 'regular polygon, regular polygon sides=4',
                 'pentagon': 'regular polygon, regular polygon sides=5',
                 'hexagon': 'regular polygon, regular polygon sides=6',
                 'septagon': 'regular polygon, regular polygon sides=7',
                 'octagon': 'regular polygon, regular polygon sides=8',
                 'point': 'circle, fill',
                 'ellipse': 'ellipse',
                 'oval': 'ellipse',
                 'diamond': 'diamond',
                 'trapezium': 'trapezium',
                 'star': 'star',
                 'circle': 'circle',
                 }

    compass_map = {'n': 'north', 'ne': 'north east', 'e': 'east',
                   'se': 'south east', 's': 'south', 'sw': 'south west',
                   'w': 'west', 'nw': 'north west', 'center': 'center'}

    def __init__(self, options=None):
        # to connect nodes they have to defined. Therefore we have to ensure
        # that code for generating nodes is outputted first.
        options = options or {}
        options['switchdraworder'] = True
        options['flattengraph'] = True
        options['rawdim'] = True
        if options.get('pgf118'):
            self.template = TIKZ118_TEMPLATE
        elif options.get('pgf210'):
            self.template = TIKZ210_TEMPLATE
        else:
            self.template = TIKZ_TEMPLATE
        DotConvBase.__init__(self, options)

        self.styles = dict(dashed='dashed', dotted='dotted',
                           bold='very thick', filled='fill', invis="", invisible="",
                           rounded='rounded corners', )
        self.dashstyles = dict(
            dashed='\\pgfsetdash{{3pt}{3pt}}{0pt}',
            dotted='\\pgfsetdash{{\\pgflinewidth}{2pt}}{0pt}',
            bold='\\pgfsetlinewidth{1.2pt}')

    def set_options(self):
        Dot2PGFConv.set_options(self)
        self.options['tikzedgelabels'] = self.options.get('tikzedgelabels', '') \
                                         or getboolattr(self.main_graph, 'd2ttikzedgelabels', '')
        self.options['styleonly'] = self.options.get('styleonly', '') \
                                    or getboolattr(self.main_graph, 'd2tstyleonly', '')
        self.options['nodeoptions'] = self.options.get('nodeoptions', '') \
                                      or getattr(self.main_graph, 'd2tnodeoptions', '')
        self.options['edgeoptions'] = self.options.get('edgeoptions', '') \
                                      or getattr(self.main_graph, 'd2tedgeoptions', '')

    def output_node_comment(self, node):
        # With the node syntax comments are unnecessary
        return ""

    def set_tikzcolor(self, color, colorname):
        res = self.convert_color(color, True)
        if len(res) == 2:
            ccolor, opacity = res
            if not (opacity == '1'):
                log.warning('Opacity not supported yet: %s', res)
        else:
            ccolor = res
        s = ""
        if ccolor.startswith('{'):
            # rgb or hsb
            s += "  \\definecolor{%s}%s;\n" % (colorname, ccolor)
            cname = colorname
        else:
            cname = color

        return s, cname

    def get_node_preproc_code(self, node):
        lblstyle = node.attr.get('lblstyle', '')

        shape = node.attr.get('shape', 'ellipse')
        shape = self.shape_map.get(shape, shape)
        # s += "%% %s\n" % (shape)
        label = node.attr.get('texlbl', '')
        style = node.attr.get('style', " ") or " "
        if lblstyle:
            if style.strip():
                style += ',' + lblstyle
            else:
                style = lblstyle

        sn = ""
        if self.options.get('styleonly'):
            sn += "\\tikz  \\node [%s] {%s};\n" % \
                  (style, label)
        else:
            sn += "\\tikz  \\node [draw,%s,%s] {%s};\n" % \
                  (shape, style, label)
        return sn

    def do_nodes(self):
        s = ""
        nodeoptions = self.options.get('nodeoptions')
        if nodeoptions:
            s += "\\begin{scope}[%s]\n" % nodeoptions
        for node in self.nodes:
            self.currentnode = node
            # detect node type
            dotshape = getattr(node, 'shape', 'ellipse')
            shape = None

            if node.attr.get('style') in ['invis', 'invisible']:
                shape = "coordinate"
            else:
                shape = self.shape_map.get(dotshape, shape)
            if shape is None:
                shape = 'ellipse'

            pos = getattr(node, 'pos', None)
            if not pos:
                continue
            x, y = pos.split(',')
            if dotshape != 'point':
                label = self.get_label(node)
            else:
                label = ''

            pos = "%sbp,%sbp" % (smart_float(x), smart_float(y))
            style = node.attr.get('style') or ""
            if node.attr.get('lblstyle'):
                if style:
                    style += ',' + node.attr['lblstyle']
                else:
                    style = node.attr['lblstyle']
            if node.attr.get('exstyle'):
                if style:
                    style += ',' + node.attr['exstyle']
                else:
                    style = node.attr['exstyle']
            sn = ""
            sn += self.output_node_comment(node)
            sn += self.start_node(node)
            if shape == "coordinate":
                sn += "  \\coordinate (%s) at (%s);\n" % (tikzify(node.name), pos)
            elif self.options.get('styleonly'):
                sn += "  \\node (%s) at (%s) [%s] {%s};\n" % \
                      (tikzify(node.name), pos, style, label)
            else:
                color = node.attr.get('color', '')
                drawstr = 'draw'
                if style.strip() == 'filled':
                    fillcolor = node.attr.get('fillcolor') or \
                                node.attr.get('color') or "gray"
                    drawstr = 'fill,draw'
                    style = ''
                    if color:
                        code, color = self.set_tikzcolor(color, 'strokecolor')
                        sn += code
                        code, fillcolor = self.set_tikzcolor(fillcolor, 'fillcolor')
                        sn += code
                        drawstr = "draw=%s,fill=%s" % (color, fillcolor)
                    else:
                        code, fillcolor = self.set_tikzcolor(fillcolor, 'fillcolor')
                        sn += code
                        drawstr = "draw,fill=%s" % fillcolor
                elif color:
                    code, color = self.set_tikzcolor(color, 'strokecolor')
                    sn += code
                    drawstr += '=' + color

                if style.strip():
                    sn += "  \\node (%s) at (%s) [%s,%s,%s] {%s};\n" % \
                          (tikzify(node.name), pos, drawstr, shape, style, label)
                else:
                    sn += "  \\node (%s) at (%s) [%s,%s] {%s};\n" % \
                          (tikzify(node.name), pos, drawstr, shape, label)
            sn += self.end_node(node)

            s += sn
        if nodeoptions:
            s += "\\end{scope}\n"
        self.body += s

    def do_edges(self):
        s = ""
        edgeoptions = self.options.get('edgeoptions')
        if edgeoptions:
            s += "\\begin{scope}[%s]\n" % edgeoptions
        for edge in self.edges:
            # general_draw_string = getattr(edge, '_draw_', "")
            label_string = getattr(edge, '_ldraw_', "")
            # head_arrow_string = getattr(edge, '_hdraw_', "")
            # tail_arrow_string = getattr(edge, '_tdraw_', "")
            tail_label_string = getattr(edge, '_tldraw_', "")
            head_label_string = getattr(edge, '_hldraw_', "")
            topath = getattr(edge, 'topath', None)
            s += self.draw_edge(edge)
            if not self.options.get('tikzedgelabels') and not topath:
                s += self.do_drawstring(label_string, edge)
                s += self.do_drawstring(tail_label_string, edge, "tailtexlbl")
                s += self.do_drawstring(head_label_string, edge, "headtexlbl")
            else:
                s += self.do_drawstring(tail_label_string, edge, "tailtexlbl")
                s += self.do_drawstring(head_label_string, edge, "headtexlbl")

        if edgeoptions:
            s += "\\end{scope}\n"
        self.body += s

    def draw_edge(self, edge):
        s = ""
        if edge.attr.get('style', '') in ['invis', 'invisible']:
            return ""
        edges = self.get_edge_points(edge)
        if len(edges) > 1:
            log.warning('The tikz output format does not support edge'
                        'concentrators yet. Expect ugly output or try the pgf or '
                        'pstricks output formats.')
        for arrowstyle, points in edges:
            # PGF uses the fill style when drawing some arrowheads. We have to
            # ensure that the fill color is the same as the pen color.
            color = edge.attr.get('color', '')
            pp = []
            for point in points:
                p = point.split(',')
                pp.append("(%sbp,%sbp)" % (smart_float(p[0]), smart_float(p[1])))

            edgestyle = edge.attr.get('style')

            styles = []
            if arrowstyle != '--':
                # styles.append(arrowstyle)
                styles = [arrowstyle]

            if edgestyle:
                edgestyles = [self.styles.get(key.strip(), key.strip()) \
                              for key in edgestyle.split(',') if key]
                styles.extend(edgestyles)

            stylestr = ",".join(styles)
            if color:
                code, color = self.set_tikzcolor(color, 'strokecolor')
                s += code
                stylestr = color + ',' + stylestr
            src = tikzify(edge.get_source())
            # check for a port
            if edge.src_port:
                src_anchor = self.compass_map.get(edge.src_port.split(':')[-1], '')
                if src_anchor:
                    src = "%s.%s" % (src, src_anchor)
            dst = tikzify(edge.get_destination())
            if edge.dst_port:
                dst_anchor = self.compass_map.get(edge.dst_port.split(':')[-1], '')
                if dst_anchor:
                    dst = "%s.%s" % (dst, dst_anchor)
            topath = edge.attr.get('topath')

            pstrs = ["%s .. controls %s and %s " % x for x in nsplit(pp, 3)]
            pstrs[0] = "(%s) ..controls %s and %s " % (src, pp[1], pp[2])
            extra = ""
            if self.options.get('tikzedgelabels') or topath:
                edgelabel = self.get_label(edge)
                # log.warning('label: %s', edgelabel)
                lblstyle = getattr(edge, 'lblstyle', '')
                exstyle = getattr(edge, 'exstyle', '')
                if exstyle:
                    if lblstyle:
                        lblstyle += ',' + exstyle
                    else:
                        lblstyle = exstyle
                if lblstyle:
                    lblstyle = '[' + lblstyle + ']'
                else:
                    lblstyle = ''
                if edgelabel:
                    extra = " node%s {%s}" % (lblstyle, edgelabel)

            if topath:
                s += "  \\draw [%s] (%s) to[%s]%s (%s);\n" % (stylestr, src,
                                                             topath, extra, dst)
            elif not self.options.get('straightedges'):
                s += "  \\draw [%s] %s ..%s (%s);\n" % (stylestr,
                                                       " .. ".join(pstrs), extra, dst)
            else:
                s += "  \\draw [%s] (%s) --%s (%s);\n" % (stylestr, src, extra, dst)

        return s

    def start_node(self, node):
        return ""

    def end_node(self, node):
        return ""


PSTRICKSN_TEMPLATE = r"""\documentclass{article}
% <<bbox>>
\usepackage[x11names, svgnames]{xcolor}
\usepackage[<<textencoding>>]{inputenc}
\usepackage{graphicx}
\usepackage{pst-all}
\usepackage[a3paper,landscape]{geometry}
\usepackage{amsmath}
<<startpreprocsection>>%
\usepackage[active,auctex]{preview}
<<endpreprocsection>>%
<<gvcols>>%
<<docpreamble>>%


\begin{document}
\pagestyle{empty}
<<startpreprocsection>>%
<<preproccode>>%
<<endpreprocsection>>%
<<startoutputsection>>%
\enlargethispage{100cm}

% Start of code
\begin{pspicture}[linewidth=1bp<<graphstyle>>]<<bbox>>
  \pstVerb{2 setlinejoin} % set line join style to 'mitre'
<<figpreamble>>%
<<drawcommands>>
<<figpostamble>>%
\end{pspicture}
% End of code
<<endoutputsection>>%
\end{document}
%
<<start_figonlysection>>
\begin{pspicture}[linewidth=1bp<<graphstyle>>]<<bbox>>
  \pstVerb{2 setlinejoin} % set line join style to 'mitre'
<<figpreamble>>%
<<drawcommands>>
<<figpostamble>>%
\end{pspicture}
<<end_figonlysection>>
%
<<startcodeonlysection>>
<<figpreamble>>%
<<drawcommands>>
<<figpostamble>>%
<<endcodeonlysection>>
"""


# ----------------- mgn --------------


class Dot2PSTricksNConv(Dot2PSTricksConv):
    """A backend that utilizes the node and edge mechanism of PSTricks-Node"""

    def __init__(self, options=None):
        options = options or {}
        # to connect nodes they have to defined. Therefore we have to ensure
        # that code for generating nodes is outputted first.
        options['switchdraworder'] = True
        options['flattengraph'] = True
        options['rawdim'] = True
        self.template = PSTRICKSN_TEMPLATE
        Dot2PSTricksConv.__init__(self, options)

    def output_node_comment(self, node):
        # With the node syntax comments are unnecessary
        return ""

    def do_nodes(self):
        s = ""
        for node in self.nodes:
            self.currentnode = node

            psshadeoption = getattr(node, 'psshadeoption', '')
            psshape = getattr(node, 'psshape', '')

            # detect node type, if psshape is not set
            if len(psshape) == 0:
                shape = getattr(node, 'shape', 'ellipse')
                # box       -> psframebox
                # circle    -> pscirclebox
                # rectangle -> psframebox
                psshape = "psframebox"
                if shape == "circle":
                    psshape = "pscirclebox"
                if shape == "ellipse":
                    psshape = "psovalbox"
                if shape == "triangle":
                    psshape = "pstribox"
                    # TODO incomplete

            width = getattr(node, 'width', '1')
            height = getattr(node, 'height', '1')
            psbox = getattr(node, 'psbox', 'false')

            color = getattr(node, 'color', '')
            fillcolor = getattr(node, 'fillcolor', '')

            if len(color) > 0:
                psshadeoption = "linecolor=" + color + "," + psshadeoption
            if len(fillcolor) > 0:
                psshadeoption = "fillcolor=" + fillcolor + "," + psshadeoption

            style = getattr(node, 'style', '')
            if len(style) > 0:
                if style == "dotted":
                    psshadeoption = "linestyle=dotted," + psshadeoption
                if style == "dashed":
                    psshadeoption = "linestyle=dashed," + psshadeoption
                if style == "solid":
                    psshadeoption = "linestyle=solid," + psshadeoption
                if style == "bold":
                    psshadeoption = "linewidth=2pt," + psshadeoption

            pos = getattr(node, 'pos')
            if not pos:
                continue
            x, y = pos.split(',')
            label = self.get_label(node)
            pos = "%sbp,%sbp" % (smart_float(x), smart_float(y))
            # TODO style

            sn = ""
            sn += self.output_node_comment(node)
            sn += self.start_node(node)
            if psbox == "false":
                sn += "\\rput(%s){\\rnode{%s}{\\%s[%s]{%s}}}\n" % \
                      (pos, tikzify(node.name), psshape, psshadeoption, label)
            else:
                sn += "\\rput(%s){\\rnode{%s}{\\%s[%s]{\\parbox[c][%sin][c]{%sin}{\\centering %s}}}}\n" % \
                      (pos, tikzify(node.name), psshape, psshadeoption, height, width, label)
            sn += self.end_node(node)
            s += sn
        self.body += s

    def do_edges(self):
        s = ""
        for edge in self.edges:
            s += self.draw_edge(edge)
        self.body += s

    def draw_edge(self, edge):
        s = ""
        edges = self.get_edge_points(edge)
        for arrowstyle, points in edges:
            # styles = []
            psarrow = getattr(edge, 'psarrow', '')

            if len(psarrow) == 0:
                stylestr = '-'
            else:
                stylestr = psarrow

            psedge = getattr(edge, 'psedge', 'ncline')
            psedgeoption = getattr(edge, 'psedgeoption', '')

            color = getattr(edge, 'color', '')
            fillcolor = getattr(edge, 'fillcolor', '')

            if len(color) > 0:
                psedgeoption = "linecolor=" + color + "," + psedgeoption
            if len(fillcolor) > 0:
                psedgeoption = "fillcolor=" + fillcolor + "," + psedgeoption

            style = getattr(edge, 'style', '')
            if len(style) > 0:
                if style == "dotted":
                    psedgeoption = "linestyle=dotted," + psedgeoption
                if style == "dashed":
                    psedgeoption = "linestyle=dashed," + psedgeoption
                if style == "solid":
                    psedgeoption = "linestyle=solid," + psedgeoption
                if style == "bold":
                    psedgeoption = "linewidth=2pt," + psedgeoption

            pslabel = getattr(edge, 'pslabel', 'ncput')
            pslabeloption = getattr(edge, 'pslabeloption', '')
            label = getattr(edge, 'label', '')
            headlabel = getattr(edge, 'headlabel', '')
            taillabel = getattr(edge, 'taillabel', '')

            src = tikzify(edge.get_source())
            dst = tikzify(edge.get_destination())
            s = "\\%s[%s]{%s}{%s}{%s}\n" % (psedge, psedgeoption, stylestr, src, dst)
            if len(label) != 0:
                s += "\\%s[%s]{%s}\n" % (pslabel, pslabeloption, label)
            if len(headlabel) != 0:
                pslabelhead = 'npos=0.8,' + pslabeloption
                s += "\\%s[%s]{%s}\n" % (pslabel, pslabelhead, headlabel)
            if len(taillabel) != 0:
                pslabeltail = 'npos=0.2,' + pslabeloption
                s += "\\%s[%s]{%s}\n" % (pslabel, pslabeltail, taillabel)
        return s

    def start_node(self, node):
        return ""

    def end_node(self, node):
        return ""


# ------------------ mgn ------------------

class PositionsDotConv(Dot2PGFConv):
    """A converter that returns a dictionary with node positions

    Returns a dictionary with node name as key and a (x, y) tuple as value.
    """

    def output(self):
        positions = {}
        for node in self.nodes:
            pos = getattr(node, 'pos', None)
            if pos:
                try:
                    positions[node.name] = [int(p) for p in pos.split(',')]
                except ValueError:
                    positions[node.name] = [float(p) for p in pos.split(',')]
        return positions


dimext = r"""
^.*? Preview:\s Snippet\s
(?P<number>\d*)\s ended.
\((?P<ht>\d*)\+(?P<dp>\d*)x(?P<wd>\d*)\)"""


class TeXDimProc:
    """Helper class for for finding the size of TeX snippets

    Uses preview.sty
    """

    # Produce document
    # Create a temporary directory
    # Compile file with latex
    # Parse log file
    # Update graph with with and height parameters
    # Clean up
    def __init__(self, template, options):
        self.template = template
        self.snippets_code = []
        self.snippets_id = []
        self.options = options
        self.dimext_re = re.compile(dimext, re.MULTILINE | re.VERBOSE)
        pass

    def add_snippet(self, snippet_id, code):
        """A a snippet of code to be processed"""
        self.snippets_id.append(snippet_id)
        self.snippets_code.append(code)

    def process(self):
        """Process all snippets of code with TeX and preview.sty

        Results are stored in the texdimlist and texdims class attributes.
        Returns False if preprocessing fails
        """
        import shutil

        if len(self.snippets_code) == 0:
            log.warning('No labels to preprocess')
            return True
        self.tempdir = tempfile.mkdtemp(prefix='dot2tex')
        log.debug('Creating temporary directroy %s' % self.tempdir)
        self.tempfilename = os.path.join(self.tempdir, 'dot2tex.tex')
        log.debug('Creating temporary file %s' % self.tempfilename)
        s = ""
        for n in self.snippets_code:
            s += "\\begin{preview}%\n"
            s += n.strip() + "%\n"
            s += "\\end{preview}%\n"
        with open(self.tempfilename, 'w') as f:
            f.write(self.template.replace('<<preproccode>>', s))
        with open(self.tempfilename, 'r') as f:
            s = f.read()
        log.debug('Code written to %s\n' % self.tempfilename + s)
        self.parse_log_file()
        shutil.rmtree(self.tempdir)
        log.debug('Temporary directory and files deleted')
        if self.texdims:
            return True
        else:
            return False
            # cleanup

    def parse_log_file(self):
        logfilename = os.path.splitext(self.tempfilename)[0] + '.log'
        tmpdir = os.getcwd()
        os.chdir(os.path.split(logfilename)[0])
        if self.options.get('usepdflatex'):
            command = 'pdflatex -interaction=nonstopmode %s' % self.tempfilename
        else:
            command = 'latex -interaction=nonstopmode %s' % self.tempfilename
        log.debug('Running command: %s' % command)

        p = Popen(command, shell=True, stdout=PIPE, stderr=PIPE, close_fds=(sys.platform != 'win32'))
        (stdout, stderr) = (p.stdout, p.stderr)
        try:
            data = stdout.read()
            log.debug("stdout from latex\n %s", data)
        finally:
            stdout.close()

        try:
            error_data = stderr.read()
            if error_data:
                log.debug('latex STDERR %s', error_data)
        finally:
            stderr.close()
        p.kill()
        p.wait()

        with open(logfilename, 'r') as f:
            logdata = f.read()
        log.debug('Logfile from LaTeX run: \n' + logdata)
        os.chdir(tmpdir)

        texdimdata = self.dimext_re.findall(logdata)
        log.debug('Texdimdata: ' + str(texdimdata))
        if len(texdimdata) == 0:
            log.error('No dimension data could be extracted from dot2tex.tex.')
            self.texdims = None
            return

        c = 1.0 / 4736286
        self.texdims = {}
        self.texdimlist = [(float(i[1]) * c, float(i[2]) * c, float(i[3]) * c) for i in texdimdata]
        self.texdims = dict(zip(self.snippets_id, self.texdimlist))


def create_options_parser():
    """Create and and return an options parser.
    """
    description = 'Convert dot files to PGF/TikZ graphics' + \
                  ' for inclusion in LaTeX.'
    parser = argparse.ArgumentParser(prog='dot2tex', description=description)

    parser.add_argument(
        '-f', '--format', action='store', dest='format',
        choices=('pstricks', 'pgf', 'pst', 'tikz', 'psn'),
        help="Set output format to 'v' (pstricks, pgf, pst, tikz, psn) ",
        metavar="v"
    )
    parser.add_argument(
        '-t', '--texmode', dest='texmode', default='verbatim',
        choices=('math', 'verbatim', 'raw'),
        help='Set text mode (verbatim, math, raw).'
    )
    parser.add_argument(
        '-d', '--duplicate', dest='duplicate', action='store_true',
        default=False, help='Try to duplicate Graphviz graphics'
    )
    parser.add_argument(
        '-s', '--straightedges', dest='straightedges', action='store_true',
        default=False, help='Force straight edges'
    )
    parser.add_argument(
        '--template', dest='templatefile', action='store',
        metavar='FILE'
    )
    parser.add_argument(
        '-o', '--output', dest='outputfile', action='store',
        metavar='FILE', default=None, help='Write output to FILE'
    )
    parser.add_argument(
        '--force', dest='force', action='store_true', default=False,
        help='Force recompilation, even if output file is newer than input file'
    )
    parser.add_argument(
        '-e', '--encoding', dest='encoding', action='store',
        choices=('utf8', 'latin1'), default=DEFAULT_TEXTENCODING,
        help='Set text encoding to utf8 or latin1'
    )
    parser.add_argument(
        '-V', '--version', dest='printversion', action='store_true',
        help='Print version information and exit', default=False
    )
    parser.add_argument(
        '-w', '--switchdraworder', dest='switchdraworder',
        action='store_true', help='Switch draw order', default=False
    ),
    parser.add_argument(
        '-p', '-c', '--preview', '--crop', dest='crop', action='store_true',
        help='Use preview.sty to crop graph', default=False
    )
    parser.add_argument(
        '--margin', dest='margin', action='store',
        help='Set preview margin', default='0pt'
    )
    parser.add_argument(
        '--docpreamble', dest='docpreamble', action='store',
        help='Insert TeX code in document preamble', metavar='TEXCODE'
    )
    parser.add_argument(
        '--figpreamble', dest='figpreamble', action='store',
        help='Insert TeX code in figure preamble', metavar='TEXCODE'
    )
    parser.add_argument(
        '--figpostamble', dest='figpostamble', action='store',
        help='Insert TeX code in figure postamble', metavar='TEXCODE'
    )
    parser.add_argument(
        '--graphstyle', dest='graphstyle', action='store',
        help='Insert graph style', metavar='STYLE'
    )
    parser.add_argument(
        '--gvcols', dest='gvcols', action='store_true',
        default=False, help='Include gvcols.tex'
    )
    parser.add_argument(
        '--figonly', dest='figonly', action='store_true',
        help='Output graph with no preamble', default=False
    )
    parser.add_argument(
        '--codeonly', dest='codeonly', action='store_true',
        help='Output only drawing commands', default=False
    )
    parser.add_argument(
        '--styleonly', dest='styleonly', action='store_true',
        help='Use style parameter only', default=False)
    parser.add_argument(
        '--debug', dest='debug', action='store_true',
        help='Show additional debugging information', default=False
    )
    parser.add_argument(
        '--preproc', dest='texpreproc', action='store_true',
        help='Preprocess graph through TeX', default=False
    )
    parser.add_argument('--alignstr', dest='alignstr', action='store')
    parser.add_argument(
        '--valignmode', dest='valignmode', default='center',
        choices=('center', 'dot'),
        help='Set vertical alginment mode  (center, dot).'
    )
    parser.add_argument(
        '--nominsize', dest='nominsize', action='store_true',
        help='No minimum node sizes', default=False
    )
    parser.add_argument(
        '--usepdflatex', dest='usepdflatex', action='store_true',
        help='Use PDFLaTeX for preprocessing', default=False
    )
    parser.add_argument(
        '--tikzedgelabels', dest='tikzedgelabels', action='store_true',
        help='Let TikZ place edge labels', default=False
    )
    parser.add_argument(
        '--nodeoptions', dest='nodeoptions', action='store',
        help='Set options for nodes', metavar='OPTIONS'
    )
    parser.add_argument(
        '--edgeoptions', dest='edgeoptions', action='store',
        help='Set options for edges', metavar='OPTIONS'
    )
    parser.add_argument(
        '--runtests', dest='runtests',
        help="Run tests", action="store_true", default=False
    )
    parser.add_argument(
        "--prog", action="store", dest="prog", default='dot',
        choices=('dot', 'neato', 'circo', 'fdp', 'twopi'),
        help='Use v to process the graph', metavar='v'
    )
    parser.add_argument(
        '--progoptions', action='store', dest='progoptions',
        default='', help='Pass options to graph layout engine',
        metavar='OPTIONS'
    )
    parser.add_argument(
        '--autosize', dest='autosize',
        help='Preprocess graph and then run Graphviz',
        action='store_true', default=False
    )
    parser.add_argument(
        '--cache', dest='cache', action='store_true', default=False
    )
    parser.add_argument(
        '--pgf118', dest='pgf118', action='store_true',
        help='Generate code compatible with PGF 1.18', default=False
    )
    parser.add_argument(
        '--pgf210', dest='pgf210', action='store_true',
        help='Generate code compatible with PGF 2.10', default=False
    )
    parser.add_argument(
        'inputfile', action='store',
        nargs='?', default=None, help='Input dot file'
    )
    return parser


def process_cmd_line():
    """Set up and parse command line options"""

    parser = create_options_parser()
    options = parser.parse_args()

    return options, parser


def _runtests():
    import doctest

    doctest.testmod()


def print_version_info():
    print("Dot2tex version % s" % __version__)


def load_dot_file(filename):
    with open(filename, 'r') as f:
        dotdata = f.readlines()
    log.info('Data read from %s' % filename)
    return dotdata


# # Program interface

def main(run_as_module=False, dotdata=None, options=None):
    """Run dot2tex and convert graph

    """
    import platform

    global log
    if not run_as_module:
        options, parser = process_cmd_line()
        # configure console logger
        console = logging.StreamHandler()
        console.setLevel(logging.WARNING)
        # set a format which is simpler for console use
        formatter = logging.Formatter('%(levelname)-8s %(message)s')
        # tell the handler to use this format
        console.setFormatter(formatter)
        log.addHandler(console)
    if options.runtests:
        log.warning('running tests')
        _runtests()
        sys.exit(0)

    if options.debug:
        # initalize log handler
        if run_as_module:
            pass
        else:
            hdlr = logging.FileHandler('dot2tex.log')
            log.addHandler(hdlr)
            formatter = logging.Formatter('%(asctime)s %(name)s %(levelname)s %(message)s')
            hdlr.setFormatter(formatter)
        log.setLevel(logging.DEBUG)
        nodebug = False
    else:
        nodebug = True

    log.info('------- Start of run -------')
    log.info("Dot2tex version % s" % __version__)
    log.info("System information:\n"
             "  Python: %s \n"
             "  Platform: %s\n"
             "  Pyparsing: %s",
             sys.version_info, platform.platform(),
             dotparsing.pyparsing_version)
    log.info('dot2tex called with: %s' % sys.argv)
    log.info('Program started in %s' % os.getcwd())
    if not run_as_module:
        if options.printversion:
            print_version_info()
            sys.exit(0)

        if options.inputfile is None:
            log.info('Data read from standard input')
            dotdata = sys.stdin.readlines()
        else:
            # exit if target file newer than dot source
            inputfile = options.inputfile
            outputfile = options.outputfile

            if outputfile is not None and options.force is False:
                input_exists = os.access(inputfile, os.F_OK)
                output_exists = os.access(outputfile, os.F_OK)

                if input_exists and output_exists:
                    input_modified_time = os.stat(inputfile)[8]
                    output_modified_time = os.stat(outputfile)[8]

                    if input_modified_time < output_modified_time:
                        print('skip: input file older than output file.')
                        sys.exit(0)
            try:
                log.debug('Attempting to read data from %s', options.inputfile)
                dotdata = load_dot_file(options.inputfile)
            except:
                if options.debug:
                    log.exception('Failed to load file %s', options.inputfile)
                else:
                    log.error('Failed to load file %s', options.inputfile)
                sys.exit(1)
    else:
        # Make sure dotdata is compatitle with the readlines data
        dotdata = dotdata.splitlines(True)

    s = ""
    # look for a line containing an \input
    m = re.search(r"^\s*\\input\{(?P<filename>.+?)\}",
                  "".join(dotdata), re.MULTILINE)
    if m:
        filename = m.group(1)
        log.info('Found \\input{%s}', filename)
        try:
            dotdata = load_dot_file(filename)
        except:
            if options.debug:
                log.exception('Failed to load \\input{%s}', filename)
            else:
                log.error('Failed to load \\input{%s}', filename)
            if run_as_module:
                raise
            else:
                sys.exit(1)

    # I'm not quite sure why this is necessary, but some files
    # produces data with line endings that confuses pydot/pyparser.
    # Note: Whitespace at end of line is sometimes significant
    log.debug('Input data:\n' + "".join(dotdata))
    lines = [line for line in dotdata if line.strip()]
    dotdata = "".join(lines)

    if options.cache and not run_as_module:
        import hashlib, cPickle

        if options.inputfile is not None and options.outputfile:
            log.info('Caching enabled')
            inputfilename = options.inputfile
            # calculate hash from command line options and dotdata
            m = hashlib.md5()
            m.update(dotdata + "".join(sys.argv))
            inputhash = m.digest()
            log.debug('Hash for %s and command line : %s', inputfilename, inputhash)
            # now look for a hash file
            hashfilename = path.join(path.dirname(inputfilename), 'dot2tex.cache')
            key = path.basename(inputfilename)
            hashes = {}
            if path.exists(hashfilename):
                log.info('Loading hash file %s', hashfilename)
                with open(hashfilename, 'r') as f:
                    try:
                        hashes = cPickle.load(f)
                    except:
                        log.exception('Failed to load hashfile')
            if hashes.get(key) == inputhash and path.exists(options.outputfile):
                log.info('Input has not changed. Will not convert input file')
                sys.exit(0)
            else:
                log.info('Hash or output file not found. Converting file')
                hashes[key] = inputhash
                with open(hashfilename, 'w') as f:
                    try:
                        cPickle.dump(hashes, f)
                    except:
                        log.warning('Failed to write hashfile')
        else:
            log.warning('You need to specify an input and output file for caching to work')

        pass

    # check for output format attribute
    fmtattr = re.findall(r'd2toutputformat=([a-z]*)', dotdata)
    extraoptions = re.findall(r'^\s*d2toptions\s*=\s*"(.*?)"\s*;?', dotdata, re.MULTILINE)
    if fmtattr:
        log.info('Found outputformat attribute: %s', fmtattr[0])
        gfmt = fmtattr[0]
    else:
        gfmt = None
    if extraoptions:
        log.debug('Found d2toptions attribute in graph: %s', extraoptions[0])
        if run_as_module:
            parser = create_options_parser()
        options = parser.parse_args(extraoptions[0].split(), options)
        if options.debug and nodebug:
            # initalize log handler
            if not run_as_module:
                hdlr = logging.FileHandler('dot2tex.log')
                log.addHandler(hdlr)
                formatter = logging.Formatter('%(asctime)s %(name)s %(levelname)s %(message)s')
                hdlr.setFormatter(formatter)
            log.setLevel(logging.DEBUG)
            nodebug = False

    output_format = options.format or gfmt or DEFAULT_OUTPUT_FORMAT
    options.format = output_format

    if output_format in ('pstricks', 'pst'):
        conv = Dot2PSTricksConv(options.__dict__)
    elif output_format == 'psn':
        conv = Dot2PSTricksNConv(options.__dict__)
    elif output_format == 'pgf':
        conv = Dot2PGFConv(options.__dict__)
    elif output_format == 'tikz':
        conv = Dot2TikZConv(options.__dict__)
    elif output_format == 'positions':
        conv = PositionsDotConv(options.__dict__)
    else:
        log.error("Unknown output format %s" % options.format)
        sys.exit(1)
    try:
        s = conv.convert(dotdata)
        log.debug('Output:\n%s', s)
        if options.autosize:
            conv.dopreproc = False
            s = conv.convert(s)
            log.debug('Output after preprocessing:\n%s', s)
        if options.outputfile:
            with open(options.outputfile, 'w') as f:
                f.write(s)
        else:
            if not run_as_module:
                print(s)
    except dotparsing.ParseException as err:
        errmsg = "Parse error:\n%s\n" % err.line + " " * (err.column - 1) + "^\n" + str(err)
        log.error(errmsg)
        if options.debug:
            log.exception('Failed to parse graph')
        if run_as_module:
            raise
        else:
            log.error(helpmsg)
    except SystemExit:
        if run_as_module:
            raise
    except:
        # log.error("Could not convert the xdot input.")
        log.exception('Failed to process input')
        if run_as_module:
            raise

    log.info('------- End of run -------')
    if run_as_module:
        return s


def convert_graph(dotsource, **kwargs):
    """Process dotsource and return LaTeX code

    Conversion options can be specified as keyword options. Example:
        convert_graph(data,format='tikz',crop=True)

    """
    parser = create_options_parser()

    options = parser.parse_args([])
    if kwargs.get('preproc', None):
        kwargs['texpreproc'] = kwargs['preproc']
        del kwargs['preproc']

    options.__dict__.update(kwargs)
    tex = main(True, dotsource, options)
    return tex


if __name__ == '__main__':
    main()
