content: ----------------------------------------------------------------------
  -- |
  -- Module      : Graphviz
  -- Maintainer  : BB
  -- Stability   : (stable)
  -- Portability : (portable)
  --
  -- > CVS $Date: 2005/09/15 18:10:44 $
  -- > CVS $Author: bringert $
  -- > CVS $Revision: 1.2 $
  --
  -- Graphviz DOT format representation and printing.
  -----------------------------------------------------------------------------
  
  module GF.Data.Graphviz (
                            Graph(..), GraphType(..),
                            Node(..), Edge(..),
                            Attr,
                            addSubGraphs,
                            setName,
                            setAttr,
                            prGraphviz
                          ) where
  
  import Data.Char
  
  import GF.Data.Utilities
  
  -- | Graph type, graph ID, graph attirbutes, graph nodes, graph edges, subgraphs
  data Graph = Graph {
                      gType :: GraphType,
                      gId :: Maybe String,
                      gAttrs :: [Attr],
                      gNodes :: [Node],
                      gEdges :: [Edge],
                      gSubgraphs :: [Graph]
                     }
    deriving (Show)
  
  data GraphType = Directed | Undirected
    deriving (Show)
  
  data Node = Node String [Attr]
    deriving Show
  
  data Edge = Edge String String [Attr]
    deriving Show
  
  type Attr = (String,String)
  
  --
  -- * Graph construction
  --
  
  addSubGraphs :: [Graph] -> Graph -> Graph
  addSubGraphs gs g = g { gSubgraphs = gs ++ gSubgraphs g }
  
  setName :: String -> Graph -> Graph
  setName n g = g { gId = Just n }
  
  setAttr :: String -> String -> Graph -> Graph
  setAttr n v g = g { gAttrs = tableSet n v (gAttrs g) }
  
  --
  -- * Pretty-printing
  --
  
  prGraphviz :: Graph -> String
  prGraphviz g@(Graph t i _ _ _ _) =
      graphtype t ++ " " ++ maybe "" esc i ++ " {\n" ++ prGraph g ++ "}\n"
  
  prSubGraph :: Graph -> String
  prSubGraph g@(Graph _ i _ _ _ _) =
      "subgraph" ++ " " ++ maybe "" esc i ++ " {\n" ++ prGraph g ++ "}"
  
  prGraph :: Graph -> String
  prGraph (Graph t id at ns es ss) =
      unlines $ map (++";") (map prAttr at
                             ++ map prNode ns
                             ++ map (prEdge t) es
                             ++ map prSubGraph ss)
  
  graphtype :: GraphType -> String
  graphtype Directed = "digraph"
  graphtype Undirected = "graph"
  
  prNode :: Node -> String
  prNode (Node n at) = esc n ++ " " ++ prAttrList at
  
  prEdge :: GraphType -> Edge -> String
  prEdge t (Edge x y at) = esc x ++ " " ++ edgeop t ++ " " ++ esc y ++ " " ++ prAttrList at
  
  edgeop :: GraphType -> String
  edgeop Directed = "->"
  edgeop Undirected = "--"
  
  prAttrList :: [Attr] -> String
  prAttrList [] = ""
  prAttrList at = "[" ++ join "," (map prAttr at) ++ "]"
  
  prAttr :: Attr -> String
  prAttr (n,v) = esc n ++ " = " ++ esc v
  
  esc :: String -> String
  esc s | needEsc s = "\"" ++ concat [ if shouldEsc c then ['\\',c] else [c] | c <- s ] ++ "\""
        | otherwise = s
    where shouldEsc = (`elem` ['"', '\\'])
  
  needEsc :: String -> Bool
  needEsc [] = True
  needEsc xs | all isDigit xs = False
  needEsc (x:xs) = not (isIDFirst x && all isIDChar xs)
  
  isIDFirst, isIDChar :: Char -> Bool
  isIDFirst c = c `elem` (['_']++['a'..'z']++['A'..'Z'])
  isIDChar c = isIDFirst c || isDigit c
  