diff --git a/Changelog b/Changelog
--- a/Changelog
+++ b/Changelog
@@ -7,6 +7,67 @@
 The following is information about what major changes have gone into
 each release.
 
+Changes in 2999.10.0.0
+----------------------
+
+* Conversion of `FGL`-style graphs to `DotRepr` values is now achieved
+  using the new `GraphvizParams` configuration type.  This allows us
+  to define a single parameter that stores all the conversion
+  functions to pass around rather than having to pass around several
+  functions.  This also allows all the non-clustered and clustered
+  functions to be collapsed together, whereas what used to be handled
+  by the primed functions is now achieved by using the
+  `setDirectedness` function.
+
+  There are three default `GraphvizParams` available:
+
+    - `defaultParams` provides some sensible defaults (no attributes
+      or clustering).
+
+    - `nonClusteredParams` is an alias of `defaultParams` where the
+      clustering type is explicitly set to be `()` for cases where you
+      don't want any clustering at all (whereas `defaultParams` allows
+      you to set your own clustering functions).
+
+    - `blankParams` sets all fields to be `undefined`; this is useful
+      for situations where you have functions that will set some
+      values for you and there is no sensible default you can use
+      (mainly for the clustering function).
+
+* Expansion of the `DotRepr` class:
+
+    - More common functions are now defined as methods (`getID`,
+      etc.).
+
+    - The ability to get more information about the structure of the
+      `DotRepr` graph, as well as where all the `DotNode`s are, etc.
+
+    - `graphNodes` now returns `DotNode`s defined only as part of
+      `DotEdge`s, and will also merge duplicate `DotNode`s together.
+
+    - `graphNodes` and `graphEdges` also return `GlobalAttributes`
+      that apply to them.
+
+* The `Point` type now only has one constructor: `Point Double
+  Double`.  The `Int`-only constructor was present due to historical
+  purposes and I thought that the `Pos` value for a `DotNode` would
+  always be a pair of `Int`s; this turns out not to be the case.
+
+* `SortV` and `PrismOverlap` now only take `Word16` values rather than
+  `Int`s, as they're not meant to allow negative values (the choice of
+  using `Word16` rather than `Word` was arbitrary, and because it's
+  unlikely those values will be large enough to require the larger
+  values available in `Word`).
+
+* `NodeCluster` has been generalised to not have to take an `LNode`
+  for the node type; the type alias `LNodeCluster` is available if you
+  still want this.
+
+* Several documentation typos fixed, including one spotted by **Kevin
+  Quick**.
+
+* The test-suite now allows running individual tests.
+
 Changes in 2999.9.0.0
 ---------------------
 
@@ -50,8 +111,8 @@
       UTF-8).  **Thanks to Jules Bean (aka quicksilver) for working
       out how to do this.**
 
-  More specifically: almost all Dot files that ship with Graphviz, as
-  documentation in the Linux kernel, etc. are now parseable.
+    More specifically: almost all Dot files that ship with Graphviz, as
+    documentation in the Linux kernel, etc. are now parseable.
 
 * A new script to assist in testing whether "real-world" Dot graphs
   are parseable.
diff --git a/Data/GraphViz.hs b/Data/GraphViz.hs
--- a/Data/GraphViz.hs
+++ b/Data/GraphViz.hs
@@ -20,13 +20,17 @@
  -}
 module Data.GraphViz
     ( -- * Conversion from graphs to /Dot/ format.
-      -- $conversion
-      graphToDot
-    , graphToDot'
+      -- ** Specifying parameters.
+      GraphvizParams(..)
+    , defaultParams
+    , nonClusteredParams
+    , blankParams
+    , setDirectedness
+      -- ** Converting graphs.
+    , graphToDot
       -- ** Conversion with support for clusters.
+    , LNodeCluster
     , NodeCluster(..)
-    , clusterGraphToDot
-    , clusterGraphToDot'
       -- ** Pseudo-inverse conversion.
     , dotToGraph
       -- * Graph augmentation.
@@ -36,15 +40,8 @@
     , AttributeEdge
       -- ** Customisable augmentation.
     , graphToGraph
-    , graphToGraph'
-    , clusterGraphToGraph
-    , clusterGraphToGraph'
       -- ** Quick augmentation.
-      -- $quickAugment
     , dotizeGraph
-    , dotizeGraph'
-    , dotizeClusterGraph
-    , dotizeClusterGraph'
       -- ** Manual augmentation.
       -- $manualAugment
     , EdgeID
@@ -89,51 +86,118 @@
       hasFlip e = Set.member (flippedEdge e) eSet
       flippedEdge (f,t,l) = (t,f,l)
 
--- | Determine if the given graph is directed.
-isDirected :: (Ord b, Graph g) => g a b -> Bool
-isDirected = not . isUndirected
-
 -- -----------------------------------------------------------------------------
 
-{- $conversion
-   There are various functions available for converting 'Graph's to
-   Graphviz's /Dot/ format (represented using the 'DotGraph' type).
-   There are two main types: converting plain graphs and converting
-   /clustered/ graphs (where the graph cluster that a particular 'Node'
-   belongs to is determined by its label).
+-- | Defines the parameters used to convert a 'Graph' into a 'DotRepr'.
+--
+--   A value of type @'GraphvizParams' nl el cl l@ indicates that the
+--   'Graph' has node labels of type @nl@, edge labels of type @el@,
+--   corresponding clusters of type @cl@ and after clustering the
+--   nodes have a label of type @l@ (which may or may not be the same
+--   as @nl@).
+--
+--   The clustering in 'clusterBy' can be to arbitrary depth.
+data GraphvizParams nl el cl l
+     = Params { -- | 'True' if the 'Graph' is directed; 'False'
+                --   otherwise.
+                isDirected       :: Bool
+                -- | The top-level global 'Attributes' for the entire
+                --   'Graph'.
+              , globalAttributes :: [GlobalAttributes]
+                -- | A function to specify which cluster a particular
+                --   'LNode' is in.
+              , clusterBy        :: (LNode nl -> LNodeCluster cl l)
+                -- | The 'GraphID' for a cluster.
+              , clusterID        :: (cl -> Maybe GraphID)
+                -- | Specify which global attributes are applied in
+                --   the given cluster.
+              , fmtCluster       :: (cl -> [GlobalAttributes])
+                -- | The specific 'Attributes' for that 'LNode'.
+              , fmtNode          :: (LNode l -> Attributes)
+                -- | The specific 'Attributes' for that 'LEdge'.
+              , fmtEdge          :: (LEdge el -> Attributes)
+              }
 
-   These functions have two versions: one in which the user specifies
-   whether the graph is directed or undirected (with a 'Bool' value of
-   'True' indicating that the graph is directed), and a primed version
-   which attempts to automatically infer if the graph is directed or not.
-   Note that these conversion functions assume that undirected graphs
-   have every edge being duplicated (or at least that if there exists an
-   edge from /n1/ to /n2/, then /n1 <= n2/; if /n1 > n2/ then it is
-   removed for an undirected graph).
--}
+-- | A default 'GraphvizParams' value which assumes the graph is
+--   directed, contains no clusters and has no 'Attribute's set.
+--
+--   If you wish to have the labels of the nodes after applying
+--   'clusterBy' to have a different from before clustering, then you
+--   will have to specify your own 'GraphvizParams' value from scratch.
+defaultParams :: GraphvizParams nl el cl nl
+defaultParams = Params { isDirected       = True
+                       , globalAttributes = []
+                       , clusterBy        = N
+                       , clusterID        = const Nothing
+                       , fmtCluster       = const []
+                       , fmtNode          = const []
+                       , fmtEdge          = const []
+                       }
 
--- | Convert a graph to Graphviz's /Dot/ format.
-graphToDot :: (Graph gr) => Bool -> gr a b -> [GlobalAttributes]
-              -> (LNode a -> Attributes) -> (LEdge b -> Attributes)
-              -> DotGraph Node
-graphToDot isDir graph gAttributes
-    = clusterGraphToDot isDir graph gAttributes clustBy cID fmtClust
-      where
-        clustBy :: LNode a -> NodeCluster () a
-        clustBy = N
-        cID = const Nothing
-        fmtClust = const []
+-- | A variant of 'defaultParams' that enforces that the clustering
+--   type is @'()'@; this avoids problems when using 'defaultParams'
+--   internally within a function without any constraint on what the
+--   clustering type is.
+nonClusteredParams :: GraphvizParams nl el () nl
+nonClusteredParams = defaultParams
 
--- | Convert a graph to Graphviz's /Dot/ format with automatic
---   direction detection.
-graphToDot'       :: (Ord b, Graph gr) => gr a b -> [GlobalAttributes]
-                     -> (LNode a -> Attributes) -> (LEdge b -> Attributes)
-                     -> DotGraph Node
-graphToDot' graph = graphToDot (isDirected graph) graph
+-- | A 'GraphvizParams' value where every field is set to
+--   @'undefined'@.  This is useful when you have a function that will
+--   set some of the values for you (e.g. 'setDirectedness') but you
+--   don't want to bother thinking of default values to set in the
+--   meantime.
+blankParams :: GraphvizParams nl el cl l
+blankParams = Params { isDirected       = undefined
+                     , globalAttributes = undefined
+                     , clusterBy        = undefined
+                     , clusterID        = undefined
+                     , fmtCluster       = undefined
+                     , fmtNode          = undefined
+                     , fmtEdge          = undefined
+                     }
 
--- | A pseudo-inverse to 'graphToDot' and 'graphToDot''; \"pseudo\" in
---   the sense that the original node and edge labels aren't able to
---   be reconstructed.
+-- | Determine if the provided 'Graph' is directed or not and set the
+--   value of 'isDirected' appropriately.
+setDirectedness             :: (Ord el, Graph gr)
+                               => (GraphvizParams nl el cl l -> gr nl el -> a)
+                               -> GraphvizParams nl el cl l -> gr nl el -> a
+setDirectedness f params gr = f params' gr
+  where
+    params' = params { isDirected = not $ isUndirected gr }
+
+-- | Convert a graph to /Dot/ format, using the specified parameters
+--   to cluster the graph, etc.
+graphToDot :: (Ord cl, Graph gr) => GraphvizParams nl el cl l
+              -> gr nl el -> DotGraph Node
+graphToDot params graph
+    = DotGraph { strictGraph     = False
+               , directedGraph   = dirGraph
+               , graphID         = Nothing
+               , graphStatements = stmts
+               }
+      where
+        dirGraph = isDirected params
+        stmts = DotStmts { attrStmts = globalAttributes params
+                         , subGraphs = cs
+                         , nodeStmts = ns
+                         , edgeStmts = es
+                         }
+        (cs, ns) = clustersToNodes (clusterBy params) (clusterID params)
+                                   (fmtCluster params) (fmtNode params)
+                                   graph
+        es = mapMaybe mkDotEdge . labEdges $ graph
+        mkDotEdge e@(f,t,_) = if dirGraph || f <= t
+                              then Just
+                                   DotEdge { edgeFromNodeID = f
+                                           , edgeToNodeID   = t
+                                           , edgeAttributes = fmtEdge params e
+                                           , directedEdge   = dirGraph
+                                           }
+                              else Nothing
+
+-- | A pseudo-inverse to 'graphToDot'; \"pseudo\" in the sense that
+--   the original node and edge labels aren't able to be
+--   reconstructed.
 dotToGraph    :: (DotRepr dg Node, Graph gr) => dg Node
                  -> gr Attributes Attributes
 dotToGraph dg = mkGraph ns' es
@@ -146,55 +210,13 @@
     nSet = Set.fromList $ map fst ns
     nEs = map (flip (,) [])
           . uniq
-          . filter (flip Set.notMember nSet)
+          . filter (`Set.notMember` nSet)
           $ concatMap (\(n1,n2,_) -> [n1,n2]) es
     ns' = ns ++ nEs
     -- Conversion functions
     toLN (DotNode n as) = (n,as)
     toLE (DotEdge f t d as) = (if d then id else (:) (t,f,as)) [(f,t,as)]
 
-
--- | Convert a graph to /Dot/ format, using the specified clustering function
---   to group nodes into clusters.
---   Clusters can be nested to arbitrary depth.
-clusterGraphToDot :: (Ord c, Graph gr) => Bool -> gr a b
-                     -> [GlobalAttributes] -> (LNode a -> NodeCluster c l)
-                     -> (c -> Maybe GraphID) -> (c -> [GlobalAttributes])
-                     -> (LNode l -> Attributes) -> (LEdge b -> Attributes)
-                     -> DotGraph Node
-clusterGraphToDot dirGraph graph gAttrs clusterBy cID fmtCluster fmtNode fmtEdge
-    = DotGraph { strictGraph     = False
-               , directedGraph   = dirGraph
-               , graphID         = Nothing
-               , graphStatements = stmts
-               }
-      where
-        stmts = DotStmts { attrStmts = gAttrs
-                         , subGraphs = cs
-                         , nodeStmts = ns
-                         , edgeStmts = es
-                         }
-        (cs, ns) = clustersToNodes clusterBy cID fmtCluster fmtNode graph
-        es = mapMaybe mkDotEdge . labEdges $ graph
-        mkDotEdge e@(f,t,_) = if dirGraph || f <= t
-                              then Just DotEdge { edgeFromNodeID = f
-                                                , edgeToNodeID   = t
-                                                , edgeAttributes = fmtEdge e
-                                                , directedEdge   = dirGraph
-                                                }
-                              else Nothing
-
--- | Convert a graph to /Dot/ format, using the specified clustering function
---   to group nodes into clusters.
---   Clusters can be nested to arbitrary depth.
---   Graph direction is automatically inferred.
-clusterGraphToDot'    :: (Ord c, Ord b, Graph gr) => gr a b
-                         -> [GlobalAttributes] -> (LNode a -> NodeCluster c l)
-                         -> (c -> Maybe GraphID) -> (c -> [GlobalAttributes])
-                         -> (LNode l -> Attributes) -> (LEdge b -> Attributes)
-                         -> DotGraph Node
-clusterGraphToDot' gr = clusterGraphToDot (isDirected gr) gr
-
 -- -----------------------------------------------------------------------------
 
 {- $augment
@@ -207,13 +229,9 @@
    'Attribute' is used to provide a unique identifier for each edge.  As
    such, you should /not/ set this 'Attribute' for any 'LEdge'.
 
-   For unprimed functions, the 'Bool' argument is 'True' for directed
-   graphs, 'False' otherwise; for the primed versions of functions the
-   directionality of the graph is automatically inferred.  Directed
-   graphs are passed through 'Dot', and undirected graphs through
-   'Neato'.
-   Note that the reason these functions do not have 'unsafePerformIO'
-   applied to them is because if you set a global 'Attribute' of:
+   Note that the reason that most of these functions do not have
+   'unsafePerformIO' applied to them is because if you set a global
+   'Attribute' of:
 
    @
     'Start' ('StartStyle' 'RandomStyle')
@@ -226,103 +244,42 @@
    use 'unsafePerformIO' directly in your own code.
 -}
 
-type AttributeNode a = (Attributes, a)
-type AttributeEdge b = (Attributes, b)
+-- | Augment the current node label type with the 'Attributes' applied
+--   to that node.
+type AttributeNode nl = (Attributes, nl)
 
--- | Run the appropriate Graphviz command on the graph to get
---   positional information and then combine that information back
---   into the original graph.
-graphToGraph :: (Graph gr) => Bool -> gr a b -> [GlobalAttributes]
-                -> (LNode a -> Attributes) -> (LEdge b -> Attributes)
-                -> IO (gr (AttributeNode a) (AttributeEdge b))
-graphToGraph isDir gr gAttributes fmtNode fmtEdge
-    = dotAttributes isDir gr' dot
-    where
-      dot = graphToDot isDir gr' gAttributes fmtNode (setEdgeComment fmtEdge)
-      gr' = addEdgeIDs gr
+-- | Augment the current edge label type with the 'Attributes' applied
+--   to that edge.
+type AttributeEdge el = (Attributes, el)
 
 -- | Run the appropriate Graphviz command on the graph to get
 --   positional information and then combine that information back
 --   into the original graph.
---   Graph direction is automatically inferred.
-graphToGraph'    :: (Ord b, Graph gr) => gr a b -> [GlobalAttributes]
-                    -> (LNode a -> Attributes) -> (LEdge b -> Attributes)
-                    -> IO (gr (AttributeNode a) (AttributeEdge b))
-graphToGraph' gr = graphToGraph (isDirected gr) gr
-
--- | Run the appropriate Graphviz command on the clustered graph to
---   get positional information and then combine that information back
---   into the original graph.
-clusterGraphToGraph :: (Ord c, Graph gr) => Bool -> gr a b
-                       -> [GlobalAttributes] -> (LNode a -> NodeCluster c l)
-                       -> (c -> Maybe GraphID) -> (c -> [GlobalAttributes])
-                       -> (LNode l -> Attributes) -> (LEdge b -> Attributes)
-                       -> IO (gr (AttributeNode a) (AttributeEdge b))
-clusterGraphToGraph isDir gr gAtts clBy cID fmtClust fmtNode fmtEdge
-    = dotAttributes isDir gr' dot
+graphToGraph :: (Ord cl, Graph gr) => GraphvizParams nl el cl l -> gr nl el
+                -> IO (gr (AttributeNode nl) (AttributeEdge el))
+graphToGraph params gr = dotAttributes (isDirected params) gr' dot
     where
-      dot = clusterGraphToDot isDir gr' gAtts clBy cID fmtClust fmtNode fmtEdge'
+      dot = graphToDot params' gr'
+      params' = params { fmtEdge = setEdgeComment $ fmtEdge params }
       gr' = addEdgeIDs gr
-      fmtEdge' = setEdgeComment fmtEdge
 
--- | Run the appropriate Graphviz command on the clustered graph to
---   get positional information and then combine that information back
---   into the original graph.
---   Graph direction is automatically inferred.
-clusterGraphToGraph'    :: (Ord b, Ord c, Graph gr) => gr a b
-                           -> [GlobalAttributes] -> (LNode a -> NodeCluster c l)
-                           -> (c -> Maybe GraphID) -> (c -> [GlobalAttributes])
-                           -> (LNode l -> Attributes) -> (LEdge b -> Attributes)
-                           -> IO (gr (AttributeNode a) (AttributeEdge b))
-clusterGraphToGraph' gr = clusterGraphToGraph (isDirected gr) gr
-
 -- -----------------------------------------------------------------------------
 
-{- $quickAugment
-   This section contains convenience functions for quick-and-dirty
-   augmentation of graphs.  No 'Attribute's are applied, and
-   'unsafePerformIO' is used to make these normal functions.  Note that
-   this should be safe since these should be referentially transparent.
--}
-
--- | Pass the graph through 'graphToGraph' with no 'Attribute's.
-dotizeGraph         :: (Graph gr) => Bool -> gr a b
-                       -> gr (AttributeNode a) (AttributeEdge b)
-dotizeGraph isDir g = unsafePerformIO
-                      $ graphToGraph isDir g gAttrs noAttrs noAttrs
-    where
-      gAttrs = []
-      noAttrs = const []
-
--- | Pass the graph through 'graphToGraph' with no 'Attribute's.
---   The graph direction is automatically inferred.
-dotizeGraph'   :: (Graph gr, Ord b) => gr a b
-                  -> gr (AttributeNode a) (AttributeEdge b)
-dotizeGraph' g = dotizeGraph (isDirected g) g
-
--- | Pass the clustered graph through 'clusterGraphToGraph' with no
---   'Attribute's.
-dotizeClusterGraph                 :: (Ord c, Graph gr) => Bool -> gr a b
-                                      -> (LNode a -> NodeCluster c l)
-                                      -> gr (AttributeNode a) (AttributeEdge b)
-dotizeClusterGraph isDir g clustBy = unsafePerformIO
-                                     $ clusterGraphToGraph isDir   g
-                                                           gAttrs  clustBy
-                                                           cID     cAttrs
-                                                           noAttrs noAttrs
+-- | This is a \"quick-and-dirty\" graph augmentation function that
+--   sets no 'Attributes' and thus should be referentially transparent
+--   and is wrapped in 'unsafePerformIO'.
+--
+--   Note that the provided 'GraphvizParams' is only used for
+--   'isDirected', 'clusterBy' and 'clusterID'.
+dotizeGraph           :: (Ord cl, Graph gr) => GraphvizParams nl el cl l
+                         -> gr nl el -> gr (AttributeNode nl) (AttributeEdge el)
+dotizeGraph params gr = unsafePerformIO
+                        $ graphToGraph params' gr
     where
-      gAttrs = []
-      cID = const Nothing
-      cAttrs = const gAttrs
-      noAttrs = const []
-
--- | Pass the clustered graph through 'clusterGraphToGraph' with no
---   'Attribute's.
---   The graph direction is automatically inferred.
-dotizeClusterGraph'   :: (Ord b, Ord c, Graph gr) => gr a b
-                         -> (LNode a -> NodeCluster c l)
-                         -> gr (AttributeNode a) (AttributeEdge b)
-dotizeClusterGraph' g = dotizeClusterGraph (isDirected g) g
+      params' = params { fmtCluster = const []
+                       , fmtNode    = const []
+                       , fmtEdge    = const []
+                       }
 
 -- -----------------------------------------------------------------------------
 
@@ -353,17 +310,17 @@
 -}
 
 -- | Used to augment an edge label with a unique identifier.
-data EdgeID b = EID { eID  :: String
-                    , eLbl :: b
-                    }
-              deriving (Eq, Ord, Show)
+data EdgeID el = EID { eID  :: String
+                     , eLbl :: el
+                     }
+               deriving (Eq, Ord, Show)
 -- Show is only provided for printing/debugging purposes when using a
 -- normal Tree-based graph.  Since it doesn't support Read, neither
 -- does EdgeID.
 
 -- | Add unique edge identifiers to each label.  This is useful for
 --   when multiple edges between two nodes need to be distinguished.
-addEdgeIDs   :: (Graph gr) => gr a b -> gr a (EdgeID b)
+addEdgeIDs   :: (Graph gr) => gr nl el -> gr nl (EdgeID el)
 addEdgeIDs g = mkGraph ns es'
   where
     ns = labNodes g
@@ -373,18 +330,18 @@
 
 -- | Add the 'Comment' to the list of attributes containing the value
 --   of the unique edge identifier.
-setEdgeComment     :: (LEdge b -> Attributes)
-                      -> (LEdge (EdgeID b) -> Attributes)
+setEdgeComment     :: (LEdge el -> Attributes)
+                      -> (LEdge (EdgeID el) -> Attributes)
 setEdgeComment f = \ e@(_,_,eid) -> Comment (eID eid) : (f . stripID) e
 
 -- | Remove the unique identifier from the 'LEdge'.
-stripID           :: LEdge (EdgeID b) -> LEdge b
+stripID           :: LEdge (EdgeID el) -> LEdge el
 stripID (f,t,eid) = (f,t, eLbl eid)
 
 -- | Pass the 'DotRepr' through the relevant command and then augment
 --   the 'Graph' that it came from.
-dotAttributes :: (Graph gr, DotRepr dg Node) => Bool -> gr a (EdgeID b)
-                 -> dg Node -> IO (gr (AttributeNode a) (AttributeEdge b))
+dotAttributes :: (Graph gr, DotRepr dg Node) => Bool -> gr nl (EdgeID el)
+                 -> dg Node -> IO (gr (AttributeNode nl) (AttributeEdge el))
 dotAttributes isDir gr dot
   = liftM (augmentGraph gr . parseDG . fromDotResult)
     $ graphvizWithHandle command dot DotOutput hGetContents'
@@ -401,14 +358,14 @@
 --   by using a conversion function or by passing the result of a
 --   conversion function through a 'GraphvizCommand' via the
 --   'DotOutput' or similar).
-augmentGraph      :: (Graph gr, DotRepr dg Node) => gr a (EdgeID b)
-                     -> dg Node -> gr (AttributeNode a) (AttributeEdge b)
+augmentGraph      :: (Graph gr, DotRepr dg Node) => gr nl (EdgeID el)
+                     -> dg Node -> gr (AttributeNode nl) (AttributeEdge el)
 augmentGraph g dg = mkGraph lns les
   where
     lns = map (\(n, l) -> (n, (nodeMap Map.! n, l)))
           $ labNodes g
     les = map augmentEdge $ labEdges g
-    augmentEdge (f,t,(EID eid l)) = (f,t, (edgeMap Map.! eid, l))
+    augmentEdge (f,t,EID eid l) = (f,t, (edgeMap Map.! eid, l))
     ns = graphNodes dg
     es = graphEdges dg
     nodeMap = Map.fromList $ map (nodeID &&& nodeAttributes) ns
@@ -459,10 +416,10 @@
 canonicalise = liftM parseDotGraph . prettyPrint
 
 -- | Quickly visualise a graph using the 'Xlib' 'GraphvizCanvas'.
-preview   :: (Ord b, Graph gr) => gr a b -> IO ()
+preview   :: (Ord el, Graph gr) => gr nl el -> IO ()
 preview g = ign $ forkIO (ign $ runGraphvizCanvas' dg Xlib)
   where
-    dg = graphToDot' g [] (const []) (const [])
+    dg = setDirectedness graphToDot nonClusteredParams g
     ign = (>> return ())
 
 -- | Used for obtaining results from 'graphvizWithHandle', etc. when
diff --git a/Data/GraphViz/AttributeGenerator.hs b/Data/GraphViz/AttributeGenerator.hs
deleted file mode 100644
--- a/Data/GraphViz/AttributeGenerator.hs
+++ /dev/null
@@ -1,512 +0,0 @@
-{- |
-   Module      : Data.GraphViz.AttributeGenerator
-   Description : Definition of the Graphviz attributes.
-   Copyright   : (c) Ivan Lazar Miljenovic
-   License     : 3-Clause BSD-style
-   Maintainer  : Ivan.Miljenovic@gmail.com
-
-   This module is a stand-alone that generates the correct code for
-   Data.GraphViz.Attributes.
-  -}
-module Data.GraphViz.AttributeGenerator where
-
-import Text.PrettyPrint
-import Data.List(transpose)
-import Data.Maybe(catMaybes, isJust, fromJust)
-import Control.Monad(liftM)
-import System.Environment(getArgs)
-
-type Code = Doc
-
--- If any args are passed in, then generate the Arbitrary instance
--- instead of the definition.
-main :: IO ()
-main = do args <- getArgs
-          let f = if null args
-                  then genCode
-                  else genArbitrary
-          print $ f att
-    where
-      att = AS { tpNm = text "Attribute"
-               , atts = attributes
-               }
-
-genCode     :: Atts -> Doc
-genCode att = vsep $ map ($att) cds
-    where
-      cds = [ createDefn
-            , createAlias
-            , showInstance
-            , parseInstance
-            , usedByFunc "Graphs" forGraphs
-            , usedByFunc "Clusters" forClusters
-            , usedByFunc "SubGraphs" forSubGraphs
-            , usedByFunc "Nodes" forNodes
-            , usedByFunc "Edges" forEdges
-            ]
-
-genArbitrary :: Atts -> Doc
-genArbitrary = arbitraryInstance
-
--- -----------------------------------------------------------------------------
-
--- Defining data structures
-
-data Atts = AS { tpNm :: Code
-               , atts :: [Attribute]
-               }
-
-data Attribute = A { cnst         :: Code
-                   , name         :: Code
-                   , parseNames   :: [Code]
-                   , valtype      :: VType
-                   , parseDef     :: Maybe Code
-                   , forGraphs    :: Bool
-                   , forClusters  :: Bool
-                   , forSubGraphs :: Bool
-                   , forNodes     :: Bool
-                   , forEdges     :: Bool
-                   , comment      :: Doc
-                   }
-
-makeAttr                   :: Constructor -> [Name] -> UsedBy -> VType
-                              -> Maybe Default -- Used when parsing the field
-                              -> Maybe Default -> Maybe Minimum -> Maybe Comment
-                              -> Attribute
-makeAttr c ns u v df d m cm = A { cnst         = text c
-                                , name         = head ns'
-                                , parseNames   = ns'
-                                , valtype      = v -- just in case need to do fancy
-                                                   -- stuff
-                                , parseDef     = liftM text df
-                                , forGraphs    = isFor 'G'
-                                , forClusters  = isFor 'C' || forSG
-                                , forSubGraphs = forSG
-                                , forNodes     = isFor 'N'
-                                , forEdges     = isFor 'E'
-                                , comment      = cm'
-                                }
-    where
-      ns' = map text ns
-      isFor f = f `elem` u
-      forSG = isFor 'S'
-      df' = if v == Bl then Just "'True'" else fmap ( \ t -> '\'' : t ++ "'") df
-      mDoc (f,fc) = f <> colon <+> text fc
-      addF f = fmap (\ dc -> (wrap (char '/') (text f), dc))
-      cm' = hsep
-            . punctuate semi
-            . map mDoc
-            $ catMaybes [ addF "Valid for" (Just u)
-                        , addF "Default" d
-                        , addF "Parsing Default" df'
-                        , addF "Minimum" m
-                        , addF "Notes" cm
-                        ]
-
-type Constructor = String
-type Name = String
-type UsedBy = String -- should only contain subset of "ENGCS"
-type Default = String
-type Minimum = String
-type Comment = String
-
-data VType = Dbl
-           | Integ
-           | Strng
-           | EStrng
-           | Bl
-           | Cust String
-             deriving (Eq, Ord, Show, Read)
-
-vtype           :: VType -> Doc
-vtype Dbl       = text "Double"
-vtype Integ     = text "Int"
-vtype Strng     = text "String"
-vtype EStrng    = text "EscString"
-vtype Bl        = text "Bool"
-vtype (Cust t)  = text t
-
-vtypeCode :: Attribute -> Code
-vtypeCode = vtype . valtype
-
--- -----------------------------------------------------------------------------
-
-createDefn     :: Atts -> Code
-createDefn att = hdr $+$ constructors $+$ derivs
-    where
-      hdr = text "data" <+> tpNm att
-      constructors = nest tab
-                     . asRows
-                     . firstOthers equals (char '|')
-                     . (++ [defUnknown])
-                     . map createDefn
-                     $ atts att
-      derivs = nest (tab + 2) $ text "deriving (Eq, Ord, Show, Read)"
-      createDefn a = [cnst a <+> vtypeCode a
-                     , if isEmpty cm
-                       then empty
-                       else text "-- ^" <+> cm
-                     ]
-          where
-            cm = comment a
-      defUnknown = [ unknownAttr <+> vtype Strng <+> vtype Strng
-                   , text "-- ^ /Valid for/: Assumed valid for all; the fields are 'Attribute' name and value respectively."
-                   ]
-
-createAlias     :: Atts -> Code
-createAlias att = text "type"
-                  <+> tp <> char 's'
-                  <+> equals
-                  <+> brackets tp
-    where
-      tp = tpNm att
-
-showInstance     :: Atts -> Code
-showInstance att = hdr $+$ insts'
-    where
-      hdr = text "instance" <+> text "PrintDot" <+> tpNm att <+> text "where"
-      var = char 'v'
-      sFunc = text "unqtDot"
-      cnct = text "<>"
-      insts = asRows
-              . (++ [unknownInst])
-              . map mkInstance
-              $ atts att
-      mkInstance a = [ sFunc <+> parens (cnst a <+> var)
-                     , equals <+> text "printField" <+> doubleQuotes (name a)
-                                  <+>  var
-                     ]
-      unknownInst = [ sFunc <+> parens (unknownAttr <+> char 'a' <+> var)
-                    , equals <+> text "toDot" <+> char 'a'
-                      <+> text "<> equals <>" <+> text "toDot" <+> var
-                    ]
-      insts' = nest tab
-              $ vsep [ insts
-                     , text "listToDot" <+> equals <+> text "unqtListToDot"
-                     ]
-
-parseInstance     :: Atts -> Code
-parseInstance att = hdr $+$ nest tab fns
-    where
-      hdr = text "instance" <+> text "ParseDot" <+> tpNm att <+> text "where"
-      fn = pFunc <+> equals <+> text "oneOf" <+> ops
-      fns = vsep [ fn
-                 , text "parse" <+> equals <+> pFunc
-                 , text "parseList" <+> equals <+> text "parseUnqtList"
-                 ]
-      ops = flip ($$) rbrack
-            . asRows
-            . firstOthers lbrack comma
-            . map return
-            . (++ [pUnknown])
-            . map parseAttr
-            $ atts att
-      pFunc = text "parseUnqt"
-      pType b a
-          | valtype a == Bl     = pFld <> text "Bool" <+> cnst a
-          | isJust $ parseDef a = pFld <> text "Def"  <+> cnst a <+> fromJust (parseDef a)
-          | otherwise           = pFld <+> cnst a
-          where
-            pFld = text "parseField" <> if b then char 's' else empty
-
-      parseAttr a = case map doubleQuotes $ parseNames a of
-                      [n] -> pType False a <+> n
-                      ns  -> pType True  a <+> docList ns
-      pUnknown = text "liftM2" <+> unknownAttr <+> text "stringBlock"
-                 <+> parens (text "parseEq >> parse")
-
-arbitraryInstance     :: Atts -> Code
-arbitraryInstance att = vsep [hdr $+$ fns, kFunc]
-    where
-      hdr = text "instance" <+> text "Arbitrary" <+> tpNm att <+> text "where"
-      fns = nest tab $ vsep [aFn, sFn]
-      aFn = aFunc <+> equals <+> text "oneof" <+> ops
-      ops = flip ($$) rbrack
-            . asRows
-            . firstOthers lbrack comma
-            . (++ [[aUnknown]])
-            . map (return . arbAttr)
-            $ atts att
-      aFunc = text "arbitrary"
-      arbAttr a = text "liftM" <+> cnst a <+> arbitraryFor' a
-      sFn = asRows
-            . (++ [sUnknown])
-            . map shrinkAttr
-            $ atts att
-      sFunc = text "shrink"
-      var = char 'v'
-      shrinkAttr a = [ sFunc <+> parens (cnst a <+> var)
-                     , equals <+> text "map" <+> cnst a
-                     , dollar <+> shrinkFor (valtype a) <+> var
-                     ]
-      aUnknown = text "liftM2" <+> unknownAttr
-                 <+> parens (text "suchThat" <+> text "arbIDString" <+> kFuncNm)
-                 <+> arbitraryFor Strng
-      sUnknown = [ sFunc <+> parens (unknownAttr <+> char 'a' <+> var)
-                 , equals <+> text "liftM2" <+> unknownAttr
-                 , parens (text "liftM" <+> parens (text "filter" <+> kFuncNm)
-                           <+> shrinkFor Strng <+> char 'a')
-                   <+> parens (shrinkFor Strng <+> var)
-                 ]
-
-      kFunc = asRows (kTpSig : kTrs ++ [kOth])
-      kFuncNm = text "validUnknown"
-      kTpSig = [ kFuncNm
-               , colon <> colon <+> text "String -> Bool"
-               ]
-      kTrs = map kTr . concatMap parseNames $ atts att
-      kTr pn = [ kFuncNm <+> doubleQuotes pn
-               , equals <+> text "False"
-               ]
-      kOth = [ kFuncNm <+> char '_'
-             , equals <+> text "True"
-             ]
-
-arbitraryFor                :: VType -> Doc
-arbitraryFor Strng          = text "arbString"
-arbitraryFor EStrng         = text "arbString"
-arbitraryFor (Cust ('[':_)) = text "arbList"
-arbitraryFor _              = text "arbitrary"
-
-arbitraryFor' :: Attribute -> Doc
-arbitraryFor' = arbitraryFor . valtype
-
-shrinkFor :: VType -> Doc
-shrinkFor (Cust ('[':_)) = text "nonEmptyShrinks"
-shrinkFor Strng          = text "shrinkString"
-shrinkFor EStrng         = text "shrinkString"
-shrinkFor _              = text "shrink"
-
-usedByFunc          :: String -> (Attribute -> Bool) -> Atts -> Code
-usedByFunc nm p att = cmnt $$ asRows (tpSig : trs ++ [fls])
-    where
-      nm' = text nm
-      dt = tpNm att
-      cmnt = text "-- | Determine if this" <+> dt
-             <+> text "is valid for use with" <+> nm' <> dot
-      tpSig = [ fn
-              , colon <> colon
-                <+> dt <+> text "->" <+> text "Bool"
-              ]
-      fn = text "usedBy" <> nm'
-      tr = text "True"
-      trs = map aTr as' ++ [unknownATr]
-      fl = text "False"
-      fls = [ fn <+> char '_'
-            , equals <+> fl
-            ]
-      as' = filter p $ atts att
-      aTr a = [ fn <+> cnst a <> braces empty
-              , equals <+> tr
-              ]
-      unknownATr = [ fn <+> unknownAttr <> braces empty
-                   , equals <+> tr
-                   ]
-
-
--- -----------------------------------------------------------------------------
-
--- Helper functions
-
--- Size of a tab character
-tab :: Int
-tab = 4
-
-firstOthers            :: Doc -> Doc -> [[Doc]] -> [[Doc]]
-firstOthers _ _ []     = []
-firstOthers f o (d:ds) = (f : d) : map ((:) o) ds
-
-wrap     :: Doc -> Doc -> Doc
-wrap w d = w <> d <> w
-
-vsep :: [Doc] -> Doc
-vsep = vcat . punctuate newline
-    where
-      newline = char '\n'
-
-asRows    :: [[Doc]] -> Doc
-asRows as = vcat $ map padR asL
-    where
-      asL = map (map (\d -> (d, docLen d))) as
-      cWidths = map (maximum . map snd) $ transpose asL
-      shiftLen rls = let (rs,ls) = unzip rls
-                     in zip rs (0:ls)
-      padR = hsep . zipWith append (0 : cWidths) . shiftLen
-      append l' (d,l) = hcat (repl (l' - l) space) <> d
-      repl n xs | n <= 0    = []
-                | otherwise = replicate n xs
-
--- A really hacky thing to do, but oh well...
--- Don't use this for multi-line Docs!
-docLen :: Doc -> Int
-docLen = length . render
-
-docList :: [Doc] -> Doc
-docList = brackets . hsep . punctuate comma
-
-dot :: Doc
-dot = char '.'
-
--- -----------------------------------------------------------------------------
-
--- The actual attributes
-
-attributes :: [Attribute]
-attributes = [ makeAttr "Damping" ["Damping"] "G" Dbl Nothing (Just "@0.99@") (Just "@0.0@") (Just "neato only")
-             , makeAttr "K" ["K"] "GC" Dbl Nothing (Just "@0.3@") (Just "@0@") (Just "sfdp, fdp only")
-             , makeAttr "URL" ["URL", "href"] "ENGC" EStrng Nothing (Just "none") Nothing (Just "svg, postscript, map only")
-             , makeAttr "ArrowHead" ["arrowhead"] "E" (Cust "ArrowType") Nothing (Just "@'normal'@") Nothing Nothing
-             , makeAttr "ArrowSize" ["arrowsize"] "E" Dbl Nothing (Just "@1.0@") (Just "@0.0@") Nothing
-             , makeAttr "ArrowTail" ["arrowtail"] "E" (Cust "ArrowType") Nothing (Just "@'normal'@") Nothing Nothing
-             , makeAttr "Aspect" ["aspect"] "G" (Cust "AspectType") Nothing Nothing Nothing (Just "dot only")
-             , makeAttr "Bb" ["bb"] "G" (Cust "Rect") Nothing Nothing Nothing (Just "write only")
-             , makeAttr "BgColor" ["bgcolor"] "GC" (Cust "Color") Nothing (Just "X11Color 'Transparent'") Nothing Nothing
-             , makeAttr "Center" ["center"] "G" Bl (Just "True") (Just "@'False'@") Nothing Nothing
-             , makeAttr "Charset" ["charset"] "G" Strng Nothing (Just "@\\\"UTF-8\\\"@") Nothing Nothing
-             , makeAttr "ClusterRank" ["clusterrank"] "G" (Cust "ClusterMode") Nothing (Just "@'Local'@") Nothing (Just "dot only")
-             , makeAttr "ColorScheme" ["colorscheme"] "ENCG" (Cust "ColorScheme") Nothing (Just "@'X11'@") Nothing Nothing
-             , makeAttr "Color" ["color"] "ENC" (Cust "[Color]") Nothing (Just "@X11Color 'Black'@") Nothing Nothing
-             , makeAttr "Comment" ["comment"] "ENG" Strng Nothing (Just "@\\\"\\\"@") Nothing Nothing
-             , makeAttr "Compound" ["compound"] "G" Bl (Just "True") (Just "@'False'@") Nothing (Just "dot only")
-             , makeAttr "Concentrate" ["concentrate"] "G" Bl (Just "True") (Just "@'False'@") Nothing Nothing
-             , makeAttr "Constraint" ["constraint"] "E" Bl (Just "True")  (Just "@'True'@") Nothing (Just "dot only")
-             , makeAttr "Decorate" ["decorate"] "E" Bl (Just "True") (Just "@'False'@") Nothing Nothing
-             , makeAttr "DefaultDist" ["defaultdist"] "G" Dbl Nothing (Just "@1+(avg. len)*sqrt(|V|)@") (Just "@epsilon@") (Just "neato only")
-             , makeAttr "Dimen" ["dimen"] "G" Integ Nothing (Just "@2@") (Just "@2@") (Just "sfdp, fdp, neato only")
-             , makeAttr "Dim" ["dim"] "G" Integ Nothing (Just "@2@") (Just "@2@") (Just "sfdp, fdp, neato only")
-             , makeAttr "Dir" ["dir"] "E" (Cust "DirType") Nothing (Just "@'Forward'@ (directed), @'NoDir'@ (undirected)") Nothing Nothing
-             , makeAttr "DirEdgeConstraints" ["diredgeconstraints"] "G" (Cust "DEConstraints") (Just "EdgeConstraints") (Just "@'NoConstraints'@") Nothing (Just "neato only")
-             , makeAttr "Distortion" ["distortion"] "N" Dbl Nothing (Just "@0.0@") (Just "@-100.0@") Nothing
-             , makeAttr "DPI" ["dpi", "resolution"] "G" Dbl Nothing (Just "@96.0@, @0.0@") Nothing (Just "svg, bitmap output only; \\\"resolution\\\" is a synonym")
-             , makeAttr "EdgeURL" ["edgeURL", "edgehref"] "E" EStrng Nothing (Just "@\\\"\\\"@") Nothing (Just "svg, map only")
-             , makeAttr "EdgeTarget" ["edgetarget"] "E" EStrng Nothing (Just "none") Nothing (Just "svg, map only")
-             , makeAttr "EdgeTooltip" ["edgetooltip"] "E" EStrng Nothing (Just "@\\\"\\\"@") Nothing (Just "svg, cmap only")
-             , makeAttr "Epsilon" ["epsilon"] "G" Dbl Nothing (Just "@.0001 * # nodes@ (@mode == 'KK'@), @.0001@ (@mode == 'Major'@)") Nothing (Just "neato only")
-             , makeAttr "ESep" ["esep"] "G" (Cust "DPoint") Nothing (Just "@+3@") Nothing (Just "not dot")
-             , makeAttr "FillColor" ["fillcolor"] "NC" (Cust "Color") Nothing (Just "@X11Color 'LightGray'@ (nodes), @X11Color 'Black'@ (clusters)") Nothing Nothing
-             , makeAttr "FixedSize" ["fixedsize"] "N" Bl (Just "True") (Just "@'False'@") Nothing Nothing
-             , makeAttr "FontColor" ["fontcolor"] "ENGC" (Cust "Color") Nothing (Just "@X11Color 'Black'@") Nothing Nothing
-             , makeAttr "FontName" ["fontname"] "ENGC" Strng Nothing (Just "@\\\"Times-Roman\\\"@") Nothing Nothing
-             , makeAttr "FontNames" ["fontnames"] "G" Strng Nothing (Just "@\\\"\\\"@") Nothing (Just "svg only")
-             , makeAttr "FontPath" ["fontpath"] "G" Strng Nothing (Just "system-dependent") Nothing Nothing
-             , makeAttr "FontSize" ["fontsize"] "ENGC" Dbl Nothing (Just "@14.0@") (Just "@1.0@") Nothing
-             , makeAttr "Group" ["group"] "N" Strng Nothing (Just "@\\\"\\\"@") Nothing (Just "dot only")
-             , makeAttr "HeadURL" ["headURL", "headhref"] "E" EStrng Nothing (Just "@\\\"\\\"@") Nothing (Just "svg, map only")
-             , makeAttr "HeadClip" ["headclip"] "E" Bl (Just "True") (Just "@'True'@") Nothing Nothing
-             , makeAttr "HeadLabel" ["headlabel"] "E" (Cust "Label") Nothing (Just "@\\\"\\\"@") Nothing Nothing
-             , makeAttr "HeadPort" ["headport"] "E" (Cust "PortPos") Nothing (Just "@'PP' 'CenterPoint'@") Nothing Nothing
-             , makeAttr "HeadTarget" ["headtarget"] "E" EStrng Nothing (Just "none") Nothing (Just "svg, map only")
-             , makeAttr "HeadTooltip" ["headtooltip"] "E" EStrng Nothing (Just "@\\\"\\\"@") Nothing (Just "svg, cmap only")
-             , makeAttr "Height" ["height"] "N" Dbl Nothing (Just "@0.5@") (Just "@0.02@") Nothing
-             , makeAttr "ID" ["id"] "GNE" (Cust "Label") Nothing (Just "@\\\"\\\"@") Nothing (Just "svg, postscript, map only")
-             , makeAttr "Image" ["image"] "N" Strng Nothing (Just "@\\\"\\\"@") Nothing Nothing
-             , makeAttr "ImageScale" ["imagescale"] "N" (Cust "ScaleType") (Just "UniformScale") (Just "@'NoScale'@") Nothing Nothing
-             , makeAttr "LabelURL" ["labelURL", "labelhref"] "E" EStrng Nothing (Just "@\\\"\\\"@") Nothing (Just "svg, map only")
-             , makeAttr "LabelAngle" ["labelangle"] "E" Dbl Nothing (Just "@-25.0@") (Just "@-180.0@") Nothing
-             , makeAttr "LabelDistance" ["labeldistance"] "E" Dbl Nothing (Just "@1.0@") (Just "@0.0@") Nothing
-             , makeAttr "LabelFloat" ["labelfloat"] "E" Bl (Just "True") (Just "@'False'@") Nothing Nothing
-             , makeAttr "LabelFontColor" ["labelfontcolor"] "E" (Cust "Color") Nothing (Just "@X11Color 'Black'@") Nothing Nothing
-             , makeAttr "LabelFontName" ["labelfontname"] "E" Strng Nothing (Just "@\\\"Times-Roman\\\"@") Nothing Nothing
-             , makeAttr "LabelFontSize" ["labelfontsize"] "E" Dbl Nothing (Just "@14.0@") (Just "@1.0@") Nothing
-             , makeAttr "LabelJust" ["labeljust"] "GC" (Cust "Justification") Nothing (Just "@'JCenter'@") Nothing Nothing
-             , makeAttr "LabelLoc" ["labelloc"] "GCN" (Cust "VerticalPlacement") Nothing (Just "@'VTop'@ (clusters), @'VBottom'@ (root graphs), @'VCenter'@ (nodes)") Nothing Nothing
-             , makeAttr "LabelTarget" ["labeltarget"] "E" EStrng Nothing (Just "none") Nothing (Just "svg, map only")
-             , makeAttr "LabelTooltip" ["labeltooltip"] "E" EStrng Nothing (Just "@\\\"\\\"@") Nothing (Just "svg, cmap only")
-             , makeAttr "Label" ["label"] "ENGC" (Cust "Label") Nothing (Just "@'StrLabel' \\\"\\N\\\"@ (nodes), @'StrLabel' \\\"\\\"@ (otherwise)") Nothing Nothing
-             , makeAttr "Landscape" ["landscape"] "G" Bl (Just "True") (Just "@'False'@") Nothing Nothing
-             , makeAttr "LayerSep" ["layersep"] "G" Strng Nothing (Just "@\\\" :\\t\\\"@") Nothing Nothing
-             , makeAttr "Layers" ["layers"] "G" (Cust "LayerList") Nothing (Just "@\\\"\\\"@") Nothing Nothing
-             , makeAttr "Layer" ["layer"] "EN" (Cust "LayerRange") Nothing (Just "@\\\"\\\"@") Nothing Nothing
-             , makeAttr "Layout" ["layout"] "G" Strng Nothing (Just "@\\\"\\\"@") Nothing Nothing
-             , makeAttr "Len" ["len"] "E" Dbl Nothing (Just "@1.0@ (neato), @0.3@ (fdp)") Nothing (Just "fdp, neato only")
-             , makeAttr "LevelsGap" ["levelsgap"] "G" Dbl Nothing (Just "@0.0@") Nothing (Just "neato only")
-             , makeAttr "Levels" ["levels"] "G" Integ Nothing (Just "@MAXINT@") (Just "@0@") (Just "sfdp only")
-             , makeAttr "LHead" ["lhead"] "E" Strng Nothing (Just "@\\\"\\\"@") Nothing (Just "dot only")
-             , makeAttr "LPos" ["lp"] "EGC" (Cust "Point") Nothing Nothing Nothing (Just "write only")
-             , makeAttr "LTail" ["ltail"] "E" Strng Nothing (Just "@\\\"\\\"@") Nothing (Just "dot only")
-             , makeAttr "Margin" ["margin"] "NG" (Cust "DPoint") Nothing (Just "device-dependent") Nothing Nothing
-             , makeAttr "MaxIter" ["maxiter"] "G" Integ Nothing (Just "@100 * # nodes@ (@mode == 'KK'@), @200@ (@mode == 'Major'@), @600@ (fdp)") Nothing (Just "fdp, neato only")
-             , makeAttr "MCLimit" ["mclimit"] "G" Dbl Nothing (Just "@1.0@") Nothing (Just "dot only")
-             , makeAttr "MinDist" ["mindist"] "G" Dbl Nothing (Just "@1.0@") (Just "@0.0@") (Just "circo only")
-             , makeAttr "MinLen" ["minlen"] "E" Integ Nothing (Just "@1@") (Just "@0@") (Just "dot only")
-             , makeAttr "Model" ["model"] "G" (Cust "Model") Nothing (Just "@'ShortPath'@") Nothing (Just "neato only")
-             , makeAttr "Mode" ["mode"] "G" (Cust "ModeType") Nothing (Just "@'Major'@") Nothing (Just "neato only")
-             , makeAttr "Mosek" ["mosek"] "G" Bl (Just "True") (Just "@'False'@") Nothing (Just "neato only; requires the Mosek software")
-             , makeAttr "NodeSep" ["nodesep"] "G" Dbl Nothing (Just "@0.25@") (Just "@0.02@") (Just "dot only")
-             , makeAttr "NoJustify" ["nojustify"] "GCNE" Bl (Just "True") (Just "@'False'@") Nothing Nothing
-             , makeAttr "Normalize" ["normalize"] "G" Bl (Just "True") (Just "@'False'@") Nothing (Just "not dot")
-             , makeAttr "Nslimit1" ["nslimit1"] "G" Dbl Nothing Nothing Nothing (Just "dot only")
-             , makeAttr "Nslimit" ["nslimit"] "G" Dbl Nothing Nothing Nothing (Just "dot only")
-             , makeAttr "Ordering" ["ordering"] "G" Strng Nothing (Just "@\\\"\\\"@") Nothing (Just "dot only")
-             , makeAttr "Orientation" ["orientation"] "N" Dbl Nothing (Just "@0.0@") (Just "@360.0@") Nothing
-             , makeAttr "OutputOrder" ["outputorder"] "G" (Cust "OutputMode") Nothing (Just "@'BreadthFirst'@") Nothing Nothing
-             , makeAttr "OverlapScaling" ["overlap_scaling"] "G" Dbl Nothing (Just "@-4@") (Just "@-1.0e10@") (Just "prism only")
-             , makeAttr "Overlap" ["overlap"] "G" (Cust "Overlap") (Just "KeepOverlaps") (Just "@'KeepOverlaps'@") Nothing (Just "not dot")
-             , makeAttr "PackMode" ["packmode"] "G" (Cust "PackMode") Nothing (Just "@'PackNode'@") Nothing (Just "not dot")
-             , makeAttr "Pack" ["pack"] "G" (Cust "Pack") (Just "DoPack") (Just "@'False'@") Nothing (Just "not dot")
-             , makeAttr "Pad" ["pad"] "G" (Cust "DPoint") Nothing (Just "@'DVal' 0.0555@ (4 points)") Nothing Nothing
-             , makeAttr "PageDir" ["pagedir"] "G" (Cust "PageDir") Nothing (Just "@'BL'@") Nothing Nothing
-             , makeAttr "Page" ["page"] "G" (Cust "Point") Nothing Nothing Nothing Nothing
-             , makeAttr "PenColor" ["pencolor"] "C" (Cust "Color") Nothing (Just "@X11Color 'Black'@") Nothing Nothing
-             , makeAttr "PenWidth" ["penwidth"] "CNE" Dbl Nothing (Just "@1.0@") (Just "@0.0@") Nothing
-             , makeAttr "Peripheries" ["peripheries"] "NC" Integ Nothing (Just "shape default (nodes), @1@ (clusters)") (Just "0") Nothing
-             , makeAttr "Pin" ["pin"] "N" Bl (Just "True") (Just "@'False'@") Nothing (Just "fdp, neato only")
-             , makeAttr "Pos" ["pos"] "EN" (Cust "Pos") Nothing Nothing Nothing Nothing
-             , makeAttr "QuadTree" ["quadtree"] "G" (Cust "QuadType") (Just "NormalQT") (Just "@'NormalQT'@") Nothing (Just "sfdp only")
-             , makeAttr "Quantum" ["quantum"] "G" Dbl Nothing (Just "@0.0@") (Just "@0.0@") Nothing
-             , makeAttr "RankDir" ["rankdir"] "G" (Cust "RankDir") Nothing (Just "@'TB'@") Nothing (Just "dot only")
-             , makeAttr "RankSep" ["ranksep"] "G" (Cust "[Double]") Nothing (Just "@0.5@ (dot), @1.0@ (twopi)") (Just "0.02") (Just "twopi, dot only")
-             , makeAttr "Rank" ["rank"] "S" (Cust "RankType") Nothing Nothing Nothing (Just "dot only")
-             , makeAttr "Ratio" ["ratio"] "G" (Cust "Ratios") Nothing Nothing Nothing Nothing
-             , makeAttr "Rects" ["rects"] "N" (Cust "Rect") Nothing Nothing Nothing (Just "write only")
-             , makeAttr "Regular" ["regular"] "N" Bl (Just "True") (Just "@'False'@") Nothing Nothing
-             , makeAttr "ReMinCross" ["remincross"] "G" Bl (Just "True") (Just "@'False'@") Nothing (Just "dot only")
-             , makeAttr "RepulsiveForce" ["repulsiveforce"] "G" Dbl Nothing (Just "@1.0@") (Just "@0.0@") (Just "sfdp only")
-             , makeAttr "Root" ["root"] "GN" (Cust "Root") (Just "IsCentral") (Just "@'NodeName' \\\"\\\"@ (graphs), @'NotCentral'@ (nodes)") Nothing (Just "circo, twopi only")
-             , makeAttr "Rotate" ["rotate"] "G" Integ Nothing (Just "@0@") Nothing Nothing
-             , makeAttr "SameHead" ["samehead"] "E" Strng Nothing (Just "@\\\"\\\"@") Nothing (Just "dot only")
-             , makeAttr "SameTail" ["sametail"] "E" Strng Nothing (Just "@\\\"\\\"@") Nothing (Just "dot only")
-             , makeAttr "SamplePoints" ["samplepoints"] "N" Integ Nothing (Just "@8@ (output), @20@ (overlap and image maps)") Nothing Nothing
-             , makeAttr "SearchSize" ["searchsize"] "G" Integ Nothing (Just "@30@") Nothing (Just "dot only")
-             , makeAttr "Sep" ["sep"] "G" (Cust "DPoint") Nothing (Just "@+4@") Nothing (Just "not dot")
-             , makeAttr "ShapeFile" ["shapefile"] "N" Strng Nothing (Just "@\\\"\\\"@") Nothing Nothing
-             , makeAttr "Shape" ["shape"] "N" (Cust "Shape") Nothing (Just "@'Ellipse'@") Nothing Nothing
-             , makeAttr "ShowBoxes" ["showboxes"] "ENG" Integ Nothing (Just "@0@") (Just "@0@") (Just "dot only")
-             , makeAttr "Sides" ["sides"] "N" Integ Nothing (Just "@4@") (Just "@0@") Nothing
-             , makeAttr "Size" ["size"] "G" (Cust "Point") Nothing Nothing Nothing Nothing
-             , makeAttr "Skew" ["skew"] "N" Dbl Nothing (Just "@0.0@") (Just "@-100.0@") Nothing
-             , makeAttr "Smoothing" ["smoothing"] "G" (Cust "SmoothType") Nothing (Just "@'NoSmooth'@") Nothing (Just "sfdp only")
-             , makeAttr "SortV" ["sortv"] "GCN" Integ Nothing (Just "@0@") (Just "@0@") Nothing
-             , makeAttr "Splines" ["splines"] "G" (Cust "EdgeType") (Just "SplineEdges") Nothing Nothing Nothing
-             , makeAttr "Start" ["start"] "G" (Cust "StartType") Nothing (Just "@\\\"\\\"@") Nothing (Just "fdp, neato only")
-             , makeAttr "StyleSheet" ["stylesheet"] "G" Strng Nothing (Just "@\\\"\\\"@") Nothing (Just "svg only")
-             , makeAttr "Style" ["style"] "ENC" (Cust "[StyleItem]") Nothing Nothing Nothing Nothing
-             , makeAttr "TailURL" ["tailURL", "tailhref"] "E" EStrng Nothing (Just "@\\\"\\\"@") Nothing (Just "svg, map only")
-             , makeAttr "TailClip" ["tailclip"] "E" Bl (Just "True") (Just "@'True'@") Nothing Nothing
-             , makeAttr "TailLabel" ["taillabel"] "E" (Cust "Label") Nothing (Just "@\\\"\\\"@") Nothing Nothing
-             , makeAttr "TailPort" ["tailport"] "E" (Cust "PortPos") Nothing (Just "center") Nothing Nothing
-             , makeAttr "TailTarget" ["tailtarget"] "E" EStrng Nothing (Just "none") Nothing (Just "svg, map only")
-             , makeAttr "TailTooltip" ["tailtooltip"] "E" EStrng Nothing (Just "@\\\"\\\"@") Nothing (Just "svg, cmap only")
-             , makeAttr "Target" ["target"] "ENGC" EStrng Nothing (Just "none") Nothing (Just "svg, map only")
-             , makeAttr "Tooltip" ["tooltip"] "NEC" EStrng Nothing (Just "@\\\"\\\"@") Nothing (Just "svg, cmap only")
-             , makeAttr "TrueColor" ["truecolor"] "G" Bl (Just "True") Nothing Nothing (Just "bitmap output only")
-             , makeAttr "Vertices" ["vertices"] "N" (Cust "[Point]") Nothing Nothing Nothing (Just "write only")
-             , makeAttr "ViewPort" ["viewport"] "G" (Cust "ViewPort") Nothing (Just "none") Nothing Nothing
-             , makeAttr "VoroMargin" ["voro_margin"] "G" Dbl Nothing (Just "@0.05@") (Just "@0.0@") (Just "not dot")
-             , makeAttr "Weight" ["weight"] "E" Dbl Nothing (Just "@1.0@") (Just "@0@ (dot), @1@ (neato,fdp,sfdp)") Nothing
-             , makeAttr "Width" ["width"] "N" Dbl Nothing (Just "@0.75@") (Just "@0.01@") Nothing
-             , makeAttr "Z" ["z"] "N" Dbl Nothing (Just "@0.0@") (Just "@-MAXFLOAT@, @-1000@") Nothing
-             ]
-
-unknownAttr :: Doc
-unknownAttr = text "UnknownAttribute"
-
-attrs = take 10 $ drop 5 attributes
-
-attrs' = AS (text "Attribute") attrs
-
-bool       :: a -> a -> Bool -> a
-bool f t b = if b then t else f
-
-dollar :: Doc
-dollar = char '$'
diff --git a/Data/GraphViz/Attributes.hs b/Data/GraphViz/Attributes.hs
--- a/Data/GraphViz/Attributes.hs
+++ b/Data/GraphViz/Attributes.hs
@@ -72,6 +72,7 @@
     ( -- * The actual /Dot/ attributes.
       Attribute(..)
     , Attributes
+    , sameAttribute
       -- ** Validity functions on @Attribute@ values.
     , usedByGraphs
     , usedBySubGraphs
@@ -173,7 +174,7 @@
 
 import Data.Char(toLower)
 import Data.Maybe(isJust)
-import Control.Arrow(first)
+import Data.Word(Word16)
 import Control.Monad(liftM, liftM2)
 
 -- -----------------------------------------------------------------------------
@@ -339,7 +340,7 @@
     | Size Point                       -- ^ /Valid for/: G
     | Skew Double                      -- ^ /Valid for/: N; /Default/: @0.0@; /Minimum/: @-100.0@
     | Smoothing SmoothType             -- ^ /Valid for/: G; /Default/: @'NoSmooth'@; /Notes/: sfdp only
-    | SortV Int                        -- ^ /Valid for/: GCN; /Default/: @0@; /Minimum/: @0@
+    | SortV Word16                     -- ^ /Valid for/: GCN; /Default/: @0@; /Minimum/: @0@
     | Splines EdgeType                 -- ^ /Valid for/: G; /Parsing Default/: 'SplineEdges'
     | Start StartType                  -- ^ /Valid for/: G; /Default/: @\"\"@; /Notes/: fdp, neato only
     | StyleSheet String                -- ^ /Valid for/: G; /Default/: @\"\"@; /Notes/: svg only
@@ -665,7 +666,7 @@
 
     parseList = parseUnqtList
 
--- | Determine if this Attribute is valid for use with Graphs.
+-- | Determine if this 'Attribute' is valid for use with Graphs.
 usedByGraphs                      :: Attribute -> Bool
 usedByGraphs Damping{}            = True
 usedByGraphs K{}                  = True
@@ -749,7 +750,7 @@
 usedByGraphs UnknownAttribute{}   = True
 usedByGraphs _                    = False
 
--- | Determine if this Attribute is valid for use with Clusters.
+-- | Determine if this 'Attribute' is valid for use with Clusters.
 usedByClusters                    :: Attribute -> Bool
 usedByClusters K{}                = True
 usedByClusters URL{}              = True
@@ -776,13 +777,13 @@
 usedByClusters UnknownAttribute{} = True
 usedByClusters _                  = False
 
--- | Determine if this Attribute is valid for use with SubGraphs.
+-- | Determine if this 'Attribute' is valid for use with SubGraphs.
 usedBySubGraphs                    :: Attribute -> Bool
 usedBySubGraphs Rank{}             = True
 usedBySubGraphs UnknownAttribute{} = True
 usedBySubGraphs _                  = False
 
--- | Determine if this Attribute is valid for use with Nodes.
+-- | Determine if this 'Attribute' is valid for use with Nodes.
 usedByNodes                    :: Attribute -> Bool
 usedByNodes URL{}              = True
 usedByNodes ColorScheme{}      = True
@@ -828,7 +829,7 @@
 usedByNodes UnknownAttribute{} = True
 usedByNodes _                  = False
 
--- | Determine if this Attribute is valid for use with Edges.
+-- | Determine if this 'Attribute' is valid for use with Edges.
 usedByEdges                    :: Attribute -> Bool
 usedByEdges URL{}              = True
 usedByEdges ArrowHead{}        = True
@@ -887,6 +888,155 @@
 usedByEdges Weight{}           = True
 usedByEdges UnknownAttribute{} = True
 usedByEdges _                  = False
+
+-- | Determine if two 'Attributes' are the same type of 'Attribute'.
+sameAttribute                                                 :: Attribute -> Attribute -> Bool
+sameAttribute Damping{}               Damping{}               = True
+sameAttribute K{}                     K{}                     = True
+sameAttribute URL{}                   URL{}                   = True
+sameAttribute ArrowHead{}             ArrowHead{}             = True
+sameAttribute ArrowSize{}             ArrowSize{}             = True
+sameAttribute ArrowTail{}             ArrowTail{}             = True
+sameAttribute Aspect{}                Aspect{}                = True
+sameAttribute Bb{}                    Bb{}                    = True
+sameAttribute BgColor{}               BgColor{}               = True
+sameAttribute Center{}                Center{}                = True
+sameAttribute Charset{}               Charset{}               = True
+sameAttribute ClusterRank{}           ClusterRank{}           = True
+sameAttribute ColorScheme{}           ColorScheme{}           = True
+sameAttribute Color{}                 Color{}                 = True
+sameAttribute Comment{}               Comment{}               = True
+sameAttribute Compound{}              Compound{}              = True
+sameAttribute Concentrate{}           Concentrate{}           = True
+sameAttribute Constraint{}            Constraint{}            = True
+sameAttribute Decorate{}              Decorate{}              = True
+sameAttribute DefaultDist{}           DefaultDist{}           = True
+sameAttribute Dimen{}                 Dimen{}                 = True
+sameAttribute Dim{}                   Dim{}                   = True
+sameAttribute Dir{}                   Dir{}                   = True
+sameAttribute DirEdgeConstraints{}    DirEdgeConstraints{}    = True
+sameAttribute Distortion{}            Distortion{}            = True
+sameAttribute DPI{}                   DPI{}                   = True
+sameAttribute EdgeURL{}               EdgeURL{}               = True
+sameAttribute EdgeTarget{}            EdgeTarget{}            = True
+sameAttribute EdgeTooltip{}           EdgeTooltip{}           = True
+sameAttribute Epsilon{}               Epsilon{}               = True
+sameAttribute ESep{}                  ESep{}                  = True
+sameAttribute FillColor{}             FillColor{}             = True
+sameAttribute FixedSize{}             FixedSize{}             = True
+sameAttribute FontColor{}             FontColor{}             = True
+sameAttribute FontName{}              FontName{}              = True
+sameAttribute FontNames{}             FontNames{}             = True
+sameAttribute FontPath{}              FontPath{}              = True
+sameAttribute FontSize{}              FontSize{}              = True
+sameAttribute Group{}                 Group{}                 = True
+sameAttribute HeadURL{}               HeadURL{}               = True
+sameAttribute HeadClip{}              HeadClip{}              = True
+sameAttribute HeadLabel{}             HeadLabel{}             = True
+sameAttribute HeadPort{}              HeadPort{}              = True
+sameAttribute HeadTarget{}            HeadTarget{}            = True
+sameAttribute HeadTooltip{}           HeadTooltip{}           = True
+sameAttribute Height{}                Height{}                = True
+sameAttribute ID{}                    ID{}                    = True
+sameAttribute Image{}                 Image{}                 = True
+sameAttribute ImageScale{}            ImageScale{}            = True
+sameAttribute LabelURL{}              LabelURL{}              = True
+sameAttribute LabelAngle{}            LabelAngle{}            = True
+sameAttribute LabelDistance{}         LabelDistance{}         = True
+sameAttribute LabelFloat{}            LabelFloat{}            = True
+sameAttribute LabelFontColor{}        LabelFontColor{}        = True
+sameAttribute LabelFontName{}         LabelFontName{}         = True
+sameAttribute LabelFontSize{}         LabelFontSize{}         = True
+sameAttribute LabelJust{}             LabelJust{}             = True
+sameAttribute LabelLoc{}              LabelLoc{}              = True
+sameAttribute LabelTarget{}           LabelTarget{}           = True
+sameAttribute LabelTooltip{}          LabelTooltip{}          = True
+sameAttribute Label{}                 Label{}                 = True
+sameAttribute Landscape{}             Landscape{}             = True
+sameAttribute LayerSep{}              LayerSep{}              = True
+sameAttribute Layers{}                Layers{}                = True
+sameAttribute Layer{}                 Layer{}                 = True
+sameAttribute Layout{}                Layout{}                = True
+sameAttribute Len{}                   Len{}                   = True
+sameAttribute LevelsGap{}             LevelsGap{}             = True
+sameAttribute Levels{}                Levels{}                = True
+sameAttribute LHead{}                 LHead{}                 = True
+sameAttribute LPos{}                  LPos{}                  = True
+sameAttribute LTail{}                 LTail{}                 = True
+sameAttribute Margin{}                Margin{}                = True
+sameAttribute MaxIter{}               MaxIter{}               = True
+sameAttribute MCLimit{}               MCLimit{}               = True
+sameAttribute MinDist{}               MinDist{}               = True
+sameAttribute MinLen{}                MinLen{}                = True
+sameAttribute Model{}                 Model{}                 = True
+sameAttribute Mode{}                  Mode{}                  = True
+sameAttribute Mosek{}                 Mosek{}                 = True
+sameAttribute NodeSep{}               NodeSep{}               = True
+sameAttribute NoJustify{}             NoJustify{}             = True
+sameAttribute Normalize{}             Normalize{}             = True
+sameAttribute Nslimit1{}              Nslimit1{}              = True
+sameAttribute Nslimit{}               Nslimit{}               = True
+sameAttribute Ordering{}              Ordering{}              = True
+sameAttribute Orientation{}           Orientation{}           = True
+sameAttribute OutputOrder{}           OutputOrder{}           = True
+sameAttribute OverlapScaling{}        OverlapScaling{}        = True
+sameAttribute Overlap{}               Overlap{}               = True
+sameAttribute PackMode{}              PackMode{}              = True
+sameAttribute Pack{}                  Pack{}                  = True
+sameAttribute Pad{}                   Pad{}                   = True
+sameAttribute PageDir{}               PageDir{}               = True
+sameAttribute Page{}                  Page{}                  = True
+sameAttribute PenColor{}              PenColor{}              = True
+sameAttribute PenWidth{}              PenWidth{}              = True
+sameAttribute Peripheries{}           Peripheries{}           = True
+sameAttribute Pin{}                   Pin{}                   = True
+sameAttribute Pos{}                   Pos{}                   = True
+sameAttribute QuadTree{}              QuadTree{}              = True
+sameAttribute Quantum{}               Quantum{}               = True
+sameAttribute RankDir{}               RankDir{}               = True
+sameAttribute RankSep{}               RankSep{}               = True
+sameAttribute Rank{}                  Rank{}                  = True
+sameAttribute Ratio{}                 Ratio{}                 = True
+sameAttribute Rects{}                 Rects{}                 = True
+sameAttribute Regular{}               Regular{}               = True
+sameAttribute ReMinCross{}            ReMinCross{}            = True
+sameAttribute RepulsiveForce{}        RepulsiveForce{}        = True
+sameAttribute Root{}                  Root{}                  = True
+sameAttribute Rotate{}                Rotate{}                = True
+sameAttribute SameHead{}              SameHead{}              = True
+sameAttribute SameTail{}              SameTail{}              = True
+sameAttribute SamplePoints{}          SamplePoints{}          = True
+sameAttribute SearchSize{}            SearchSize{}            = True
+sameAttribute Sep{}                   Sep{}                   = True
+sameAttribute ShapeFile{}             ShapeFile{}             = True
+sameAttribute Shape{}                 Shape{}                 = True
+sameAttribute ShowBoxes{}             ShowBoxes{}             = True
+sameAttribute Sides{}                 Sides{}                 = True
+sameAttribute Size{}                  Size{}                  = True
+sameAttribute Skew{}                  Skew{}                  = True
+sameAttribute Smoothing{}             Smoothing{}             = True
+sameAttribute SortV{}                 SortV{}                 = True
+sameAttribute Splines{}               Splines{}               = True
+sameAttribute Start{}                 Start{}                 = True
+sameAttribute StyleSheet{}            StyleSheet{}            = True
+sameAttribute Style{}                 Style{}                 = True
+sameAttribute TailURL{}               TailURL{}               = True
+sameAttribute TailClip{}              TailClip{}              = True
+sameAttribute TailLabel{}             TailLabel{}             = True
+sameAttribute TailPort{}              TailPort{}              = True
+sameAttribute TailTarget{}            TailTarget{}            = True
+sameAttribute TailTooltip{}           TailTooltip{}           = True
+sameAttribute Target{}                Target{}                = True
+sameAttribute Tooltip{}               Tooltip{}               = True
+sameAttribute TrueColor{}             TrueColor{}             = True
+sameAttribute Vertices{}              Vertices{}              = True
+sameAttribute ViewPort{}              ViewPort{}              = True
+sameAttribute VoroMargin{}            VoroMargin{}            = True
+sameAttribute Weight{}                Weight{}                = True
+sameAttribute Width{}                 Width{}                 = True
+sameAttribute Z{}                     Z{}                     = True
+sameAttribute (UnknownAttribute a1 _) (UnknownAttribute a2 _) = a1 == a2
+sameAttribute _                       _                       = False
 {- Delete to here -}
 
 -- -----------------------------------------------------------------------------
@@ -1349,13 +1499,11 @@
 
 -- -----------------------------------------------------------------------------
 
-data Point = Point Int Int
-           | PointD Double Double
+data Point = Point Double Double
              deriving (Eq, Ord, Show, Read)
 
 instance PrintDot Point where
     unqtDot (Point  x y) = commaDel x y
-    unqtDot (PointD x y) = commaDel x y
 
     toDot = doubleQuotes . unqtDot
 
@@ -1368,16 +1516,7 @@
     -- integer, second a double: if Point parsing first, then it won't
     -- parse the second number properly; but if PointD first then it
     -- will treat Int/Int as Double/Double.
-    parseUnqt = intDblPoint
-                `onFail`
-                liftM (uncurry Point)  commaSepUnqt
-                `onFail`
-                liftM (uncurry PointD) commaSepUnqt
-        where
-          intDblPoint = liftM (uncurry PointD . first fI)
-                        $ commaSep' parseUnqt parseStrictFloat
-          fI :: Int -> Double
-          fI = fromIntegral
+    parseUnqt = liftM (uncurry Point) commaSepUnqt
 
     parse = quotedParse parseUnqt
 
@@ -1389,7 +1528,10 @@
              | RemoveOverlaps
              | ScaleOverlaps
              | ScaleXYOverlaps
-             | PrismOverlap (Maybe Int) -- ^ Only when sfdp is available, 'Int' is non-negative
+             | PrismOverlap (Maybe Word16) -- ^ Only when sfdp is
+                                           --   available, @'Nothing'@
+                                           --   is equivalent to
+                                           --   @'Just' 1000@.
              | CompressOverlap
              | VpscOverlap
              | IpsepOverlap -- ^ Only when @mode == 'IpSep'@
diff --git a/Data/GraphViz/Commands.hs b/Data/GraphViz/Commands.hs
--- a/Data/GraphViz/Commands.hs
+++ b/Data/GraphViz/Commands.hs
@@ -154,7 +154,7 @@
                     | PlainExt  -- ^ As for 'Plain', but provides port
                                 --   names on head and tail nodes when
                                 --   applicable.
-                    | Png       -- ^ Portable Network Graphvics format.
+                    | Png       -- ^ Portable Network Graphics format.
                     | Ps        -- ^ PostScript.
                     | Ps2       -- ^ PostScript for PDF.
                     | Svg       -- ^ Scalable Vector Graphics format.
diff --git a/Data/GraphViz/Parsing.hs b/Data/GraphViz/Parsing.hs
--- a/Data/GraphViz/Parsing.hs
+++ b/Data/GraphViz/Parsing.hs
@@ -166,7 +166,7 @@
 
 instance ParseDot Char where
     -- Can't be a quote character.
-    parseUnqt = satisfy ((/=) quoteChar)
+    parseUnqt = satisfy (quoteChar /=)
 
     parse = satisfy restIDString
             `onFail`
diff --git a/Data/GraphViz/PreProcessing.hs b/Data/GraphViz/PreProcessing.hs
--- a/Data/GraphViz/PreProcessing.hs
+++ b/Data/GraphViz/PreProcessing.hs
@@ -81,10 +81,10 @@
     where
       start = string "/*"
       end = string "*/"
-      inner = many1 (satisfy ((/=) '*'))
+      inner = many1 (satisfy ('*' /=))
               `onFail`
               do ast <- character '*'
-                 n <- satisfy ((/=) '/')
+                 n <- satisfy ('/' /=)
                  liftM ((:) ast . (:) n) inner
 
 parseConcatStrings :: Parse String
@@ -96,7 +96,7 @@
                  `onFail`
                  parseSplitLine -- in case there's a split mid-quote
                  `onFail`
-                 liftM return (satisfy ((/=) quoteChar))
+                 liftM return (satisfy (quoteChar /=))
     parseConcat = parseSep >> character '+' >> parseSep
     parseSep = many $ allWhitespace `onFail` parseUnwanted
     wrapQuotes str = quoteChar : str ++ [quoteChar]
diff --git a/Data/GraphViz/Testing.hs b/Data/GraphViz/Testing.hs
--- a/Data/GraphViz/Testing.hs
+++ b/Data/GraphViz/Testing.hs
@@ -40,11 +40,12 @@
 -}
 module Data.GraphViz.Testing
        ( -- * Running the test suite.
-         runDefaultTests
+         runChosenTests
        , runTests
        , runTest
          -- ** The tests themselves
        , Test(..)
+       , defaultTests
        , test_printParseID_Attributes
        , test_generalisedSameDot
        , test_printParseID
@@ -91,19 +92,16 @@
 
 -- -----------------------------------------------------------------------------
 
-runDefaultTests :: IO ()
-runDefaultTests = do putStrLn msg
-                     blankLn
-                     runTests defaultTests
-                     spacerLn
-                     putStrLn successMsg
+runChosenTests       :: [Test] -> IO ()
+runChosenTests tests = do putStrLn msg
+                          blankLn
+                          runTests tests
+                          spacerLn
+                          putStrLn successMsg
   where
     msg = "This is the test suite for the graphviz library.\n\
            \If any of these tests fail, please inform the maintainer,\n\
-           \including full output of this test suite.\n\
-           \\n\
-           \This test suite takes approximately 55 minutes to run on a\n\
-           \2 GHz Mobile Core 2 Duo (running with a single thread)."
+           \including full output of this test suite."
 
     successMsg = "All tests were successful!"
 
@@ -112,9 +110,10 @@
 -- Defining a Test structure and how to run tests.
 
 -- | Defines the test structure being used.
-data Test = Test { name :: String
-                 , desc :: String
-                 , test :: IO Result -- ^ QuickCheck test.
+data Test = Test { name       :: String
+                 , lookupName :: String    -- ^ Should be lowercase
+                 , desc       :: String
+                 , test       :: IO Result -- ^ QuickCheck test.
                  }
 
 -- | Run all of the provided tests.
@@ -168,14 +167,23 @@
                  -- , test_parsePrettyID
                , test_dotizeAugment
                , test_dotizeAugmentUniq
+               , test_findAllNodes
+               , test_findAllNodesG
+               , test_findAllNodesE
+               , test_findAllNodesEG
+               , test_findAllEdges
+               , test_findAllEdgesG
+               , test_noGraphInfo
+               , test_noGraphInfoG
                ]
 
 -- | Test that 'Attributes' can be printed and then parsed back.
 test_printParseID_Attributes :: Test
 test_printParseID_Attributes
-  = Test { name = "Printing and parsing of Attributes"
-         , desc = dsc
-         , test = quickCheckWithResult args prop
+  = Test { name       = "Printing and parsing of Attributes"
+         , lookupName = "attributes"
+         , desc       = dsc
+         , test       = quickCheckWithResult args prop
          }
     where
       prop :: Attributes -> Property
@@ -192,9 +200,10 @@
 
 test_generalisedSameDot :: Test
 test_generalisedSameDot
-  = Test { name = "Printing generalised Dot code"
-         , desc = dsc
-         , test = quickCheckResult prop
+  = Test { name       = "Printing generalised Dot code"
+         , lookupName = "makegeneralised"
+         , desc       = dsc
+         , test       = quickCheckResult prop
          }
     where
       prop :: DotGraph Int -> Bool
@@ -205,9 +214,10 @@
 
 test_printParseID :: Test
 test_printParseID
-  = Test { name = "Printing and Parsing DotGraphs"
-         , desc = dsc
-         , test = quickCheckResult prop
+  = Test { name       = "Printing and Parsing DotGraphs"
+         , lookupName = "printparseid"
+         , desc       = dsc
+         , test       = quickCheckResult prop
          }
     where
       prop :: DotGraph Int -> Bool
@@ -219,9 +229,10 @@
 
 test_printParseGID :: Test
 test_printParseGID
-  = Test { name = "Printing and Parsing Generalised DotGraphs"
-         , desc = dsc
-         , test = quickCheckResult prop
+  = Test { name       = "Printing and Parsing Generalised DotGraphs"
+         , lookupName = "printparseidg"
+         , desc       = dsc
+         , test       = quickCheckResult prop
          }
     where
       prop :: GDotGraph Int -> Bool
@@ -233,9 +244,10 @@
 
 test_preProcessingID :: Test
 test_preProcessingID
-  = Test { name = "Pre-processing Dot code"
-         , desc = dsc
-         , test = quickCheckResult prop
+  = Test { name       = "Pre-processing Dot code"
+         , lookupName = "preprocessing"
+         , desc       = dsc
+         , test       = quickCheckResult prop
          }
     where
       prop :: DotGraph Int -> Bool
@@ -251,9 +263,10 @@
 -- | This test is not valid for use until valid DotGraphs can be generated.
 test_parsePrettyID :: Test
 test_parsePrettyID
-  = Test { name = "Parsing pretty-printed code"
-         , desc = dsc
-         , test = quickCheckResult prop
+  = Test { name       = "Parsing pretty-printed code"
+         , lookupName = "parsepretty"
+         , desc       = dsc
+         , test       = quickCheckResult prop
          }
     where
       prop :: DotGraph Int -> Bool
@@ -267,9 +280,10 @@
 
 test_dotizeAugment :: Test
 test_dotizeAugment
-  = Test { name = "Augmenting FGL Graphs"
-         , desc = dsc
-         , test = quickCheckResult prop
+  = Test { name       = "Augmenting FGL Graphs"
+         , lookupName = "augment"
+         , desc       = dsc
+         , test       = quickCheckResult prop
          }
     where
       prop :: Gr Char Double -> Bool
@@ -282,9 +296,10 @@
 
 test_dotizeAugmentUniq :: Test
 test_dotizeAugmentUniq
-  = Test { name = "Unique edges in augmented FGL Graphs"
-         , desc = dsc
-         , test = quickCheckResult prop
+  = Test { name       = "Unique edges in augmented FGL Graphs"
+         , lookupName = "augmentuniq"
+         , desc       = dsc
+         , test       = quickCheckResult prop
          }
     where
       prop :: Gr Char Double -> Bool
@@ -295,3 +310,122 @@
              \then each edge between two nodes should have a unique position\n\
              \Attribute, etc."
 
+test_findAllNodes :: Test
+test_findAllNodes
+  = Test { name       = "Ensure all nodes are found in a DotGraph"
+         , lookupName = "findnodes"
+         , desc       = dsc
+         , test       = quickCheckResult prop
+         }
+    where
+      prop :: Gr () () -> Bool
+      prop = prop_findAllNodes
+
+      dsc = "nodeInformation should find all nodes in a DotGraph;\n\
+             \this is tested by converting an FGL graph and comparing\n\
+             \the nodes it should have to those that are found."
+
+test_findAllNodesG :: Test
+test_findAllNodesG
+  = Test { name       = "Ensure all nodes are found in a GDotGraph"
+         , lookupName = "findnodesg"
+         , desc       = dsc
+         , test       = quickCheckResult prop
+         }
+    where
+      prop :: Gr () () -> Bool
+      prop = prop_findAllNodesG
+
+      dsc = "nodeInformation should find all nodes in a GDotGraph;\n\
+             \this is tested by converting an FGL graph and comparing\n\
+             \the nodes it should have to those that are found."
+
+test_findAllNodesE :: Test
+test_findAllNodesE
+  = Test { name       = "Ensure all nodes are found in a node-less DotGraph"
+         , lookupName = "findedgelessnodes"
+         , desc       = dsc
+         , test       = quickCheckResult prop
+         }
+    where
+      prop :: Gr () () -> Bool
+      prop = prop_findAllNodesE
+
+      dsc = "nodeInformation should find all nodes in a DotGraph,\n\
+             \even if there are no explicit nodes in that graph.\n\
+             \This is tested by converting an FGL graph and comparing\n\
+             \the nodes it should have to those that are found."
+
+test_findAllNodesEG :: Test
+test_findAllNodesEG
+  = Test { name       = "Ensure all nodes are found in a node-less GDotGraph"
+         , lookupName = "findedgelessnodesg"
+         , desc       = dsc
+         , test       = quickCheckResult prop
+         }
+    where
+      prop :: Gr () () -> Bool
+      prop = prop_findAllNodesEG
+
+      dsc = "nodeInformation should find all nodes in a GDotGraph,\n\
+             \even if there are no explicit nodes in that graph.\n\
+             \This is tested by converting an FGL graph and comparing\n\
+             \the nodes it should have to those that are found."
+
+test_findAllEdges :: Test
+test_findAllEdges
+  = Test { name       = "Ensure all edges are found in a DotGraph"
+         , lookupName = "findedges"
+         , desc       = dsc
+         , test       = quickCheckResult prop
+         }
+    where
+      prop :: Gr () () -> Bool
+      prop = prop_findAllEdges
+
+      dsc = "nodeInformation should find all edges in a DotGraph;\n\
+             \this is tested by converting an FGL graph and comparing\n\
+             \the edges it should have to those that are found."
+
+test_findAllEdgesG :: Test
+test_findAllEdgesG
+  = Test { name       = "Ensure all edges are found in a GDotGraph"
+         , lookupName = "findedgesg"
+         , desc       = dsc
+         , test       = quickCheckResult prop
+         }
+    where
+      prop :: Gr () () -> Bool
+      prop = prop_findAllEdgesG
+
+      dsc = "nodeInformation should find all edges in a GDotGraph;\n\
+             \this is tested by converting an FGL graph and comparing\n\
+             \the edges it should have to those that are found."
+
+test_noGraphInfo :: Test
+test_noGraphInfo
+  = Test { name       = "Plain DotGraphs should have no structural information"
+         , lookupName = "nographinfo"
+         , desc       = dsc
+         , test       = quickCheckResult prop
+         }
+    where
+      prop :: Gr () () -> Bool
+      prop = prop_noGraphInfo
+
+      dsc = "When converting a Graph to a DotGraph, there should be no\n\
+             \clusters or global attributes."
+
+test_noGraphInfoG :: Test
+test_noGraphInfoG
+  = Test { name       = "Plain GDotGraphs should have no structural information"
+         , lookupName = "nographinfog"
+         , desc       = dsc
+         , test       = quickCheckResult prop
+         }
+    where
+      prop :: Gr () () -> Bool
+      prop = prop_noGraphInfoG
+
+      dsc = "When converting a Graph to a GDotGraph, there should be no\n\
+             \clusters or global attributes."
diff --git a/Data/GraphViz/Testing/Instances/FGL.hs b/Data/GraphViz/Testing/Instances/FGL.hs
--- a/Data/GraphViz/Testing/Instances/FGL.hs
+++ b/Data/GraphViz/Testing/Instances/FGL.hs
@@ -43,5 +43,5 @@
 
   shrink gr = case nodes gr of
                    -- Need to have at least 2 nodes before we delete one!
-                   ns@(_:_:_) -> map (flip delNode gr) ns
+                   ns@(_:_:_) -> map (`delNode` gr) ns
                    _          -> []
diff --git a/Data/GraphViz/Testing/Properties.hs b/Data/GraphViz/Testing/Properties.hs
--- a/Data/GraphViz/Testing/Properties.hs
+++ b/Data/GraphViz/Testing/Properties.hs
@@ -9,8 +9,12 @@
 -}
 module Data.GraphViz.Testing.Properties where
 
-import Data.GraphViz(dotizeGraph', prettyPrint')
-import Data.GraphViz.Types(DotRepr, DotGraph, printDotGraph)
+import Data.GraphViz( dotizeGraph, graphToDot
+                    , setDirectedness, nonClusteredParams, prettyPrint')
+import Data.GraphViz.Types( DotRepr, DotGraph(..), DotStatements(..)
+                          , DotNode(..), DotEdge(..), GlobalAttributes(..)
+                          , printDotGraph, graphNodes, graphEdges
+                          , graphStructureInformation)
 import Data.GraphViz.Types.Generalised(generaliseDotGraph)
 import Data.GraphViz.Printing(PrintDot(..), printIt)
 import Data.GraphViz.Parsing(ParseDot(..), parseIt, parseIt')
@@ -19,8 +23,13 @@
 
 import Test.QuickCheck
 
-import Data.Graph.Inductive(DynGraph, equal, nmap, emap, labEdges)
-import Data.List(nub)
+import Data.Graph.Inductive( Graph, DynGraph
+                           , equal, nmap, emap, labEdges, nodes, edges)
+import Data.List(nub, sort)
+import Data.Function(on)
+import qualified Data.Map as Map
+import qualified Data.Set as Set
+import Control.Arrow((&&&))
 
 -- -----------------------------------------------------------------------------
 -- The properties to test for
@@ -38,7 +47,8 @@
 
 -- | When converting a 'DotGraph' value to a 'GDotGraph' one, they
 --   should generate the same Dot code.
-prop_generalisedSameDot    :: (ParseDot n, PrintDot n) => DotGraph n -> Bool
+prop_generalisedSameDot    :: (Ord n, ParseDot n, PrintDot n)
+                              => DotGraph n -> Bool
 prop_generalisedSameDot dg = printDotGraph dg == printDotGraph gdg
   where
     gdg = generaliseDotGraph dg
@@ -57,16 +67,16 @@
                          => dg n -> Bool
 prop_parsePrettyID dg = (parseIt' . prettyPrint') dg == dg
 
--- | This property verifies that 'dotizeGraph'', etc. only /augment/ the
+-- | This property verifies that 'dotizeGraph', etc. only /augment/ the
 --   original graph; that is, the actual nodes, edges and labels for
---   each remain unchanged.  Whilst 'dotize'', etc. only require
+--   each remain unchanged.  Whilst 'dotize', etc. only require
 --   'Graph' instances, this property requires 'DynGraph' (which is a
 --   sub-class of 'Graph') instances to be able to strip off the
 --   'Attributes' augmentations.
 prop_dotizeAugment   :: (DynGraph g, Eq n, Ord e) => g n e -> Bool
 prop_dotizeAugment g = equal g (unAugment g')
   where
-    g' = dotizeGraph' g
+    g' = setDirectedness dotizeGraph nonClusteredParams g
     unAugment = nmap snd . emap snd
 
 -- | When a graph with multiple edges is augmented, then all edges
@@ -76,11 +86,96 @@
 prop_dotizeAugmentUniq   :: (DynGraph g, Eq n, Ord e) => g n e -> Bool
 prop_dotizeAugmentUniq g = all uniqLs lss
   where
-    g' = dotizeGraph' g
+    g' = setDirectedness dotizeGraph nonClusteredParams g
     les = map (\(f,t,l) -> ((f,t),l)) $ labEdges g'
     lss = map (map snd) . filter (not . isSingle)
           $ groupSortBy fst les
     uniqLs ls = ls == nub ls
+
+-- | Ensure that the definition of 'nodeInformation' for 'DotGraph'
+--   finds all the nodes.
+prop_findAllNodes   :: (Ord el, Graph g) => g nl el -> Bool
+prop_findAllNodes g = ((==) `on` sort) gns dgns
+  where
+    gns = nodes g
+    dg = setDirectedness graphToDot nonClusteredParams g
+    dgns = map nodeID $ graphNodes dg
+
+-- | Ensure that the definition of 'nodeInformation' for 'GDotGraph'
+--   finds all the nodes.
+prop_findAllNodesG   :: (Ord el, Graph g) => g nl el -> Bool
+prop_findAllNodesG g = ((==) `on` sort) gns dgns
+  where
+    gns = nodes g
+    dg = generaliseDotGraph $ setDirectedness graphToDot nonClusteredParams g
+    dgns = map nodeID $ graphNodes dg
+
+-- | Ensure that the definition of 'nodeInformation' for 'DotGraph'
+--   finds all the nodes when the explicit 'DotNode' definitions are
+--   removed.
+prop_findAllNodesE   :: (Ord el, Graph g) => g nl el -> Bool
+prop_findAllNodesE g = ((==) `on` sort) gns dgns
+  where
+    gns = nodes g
+    dg = removeNodes $ setDirectedness graphToDot nonClusteredParams g
+    dgns = map nodeID $ graphNodes dg
+    removeNodes dot@DotGraph{graphStatements = stmts}
+      = dot { graphStatements
+               = stmts {nodeStmts = filter notInEdge $ nodeStmts stmts}
+            }
+    gnes = Set.fromList . concatMap (\(f,t) -> [f,t]) $ edges g
+    notInEdge dn = nodeID dn `Set.notMember` gnes
+
+-- | Ensure that the definition of 'nodeInformation' for 'GDotGraph'
+--   finds all the nodes when the explicit 'DotNode' definitions are
+--   removed.
+prop_findAllNodesEG   :: (Ord el, Graph g) => g nl el -> Bool
+prop_findAllNodesEG g = ((==) `on` sort) gns dgns
+  where
+    gns = nodes g
+    dg = generaliseDotGraph . removeNodes
+         $ setDirectedness graphToDot nonClusteredParams g
+    dgns = map nodeID $ graphNodes dg
+    removeNodes dot@DotGraph{graphStatements = stmts}
+      = dot { graphStatements
+               = stmts {nodeStmts = filter notInEdge $ nodeStmts stmts}
+            }
+    gnes = Set.fromList . concatMap (\(f,t) -> [f,t]) $ edges g
+    notInEdge dn = nodeID dn `Set.notMember` gnes
+
+-- | Ensure that the definition of 'edgeInformation' for 'DotGraph'
+--   finds all the nodes.
+prop_findAllEdges   :: (Ord el, Graph g) => g nl el -> Bool
+prop_findAllEdges g = ((==) `on` sort) ges dges
+  where
+    ges = edges g
+    dg = graphToDot nonClusteredParams g
+    dges = map (edgeFromNodeID &&& edgeToNodeID) $ graphEdges dg
+
+-- | Ensure that the definition of 'edgeInformation' for 'GDotGraph'
+--   finds all the nodes.
+prop_findAllEdgesG   :: (Ord el, Graph g) => g nl el -> Bool
+prop_findAllEdgesG g = ((==) `on` sort) ges dges
+  where
+    ges = edges g
+    dg = generaliseDotGraph $ graphToDot nonClusteredParams g
+    dges = map (edgeFromNodeID &&& edgeToNodeID) $ graphEdges dg
+
+-- | There should be no clusters or global attributes when converting
+--   a 'Graph' to a 'DotGraph' without any formatting or clustering.
+prop_noGraphInfo   :: (Ord el, Graph g) => g nl el -> Bool
+prop_noGraphInfo g = info == (GraphAttrs [], Map.empty)
+  where
+    dg = setDirectedness graphToDot nonClusteredParams g
+    info = graphStructureInformation dg
+
+-- | There should be no clusters or global attributes when converting
+--   a 'Graph' to a 'GDotGraph' without any formatting or clustering.
+prop_noGraphInfoG   :: (Ord el, Graph g) => g nl el -> Bool
+prop_noGraphInfoG g = info == (GraphAttrs [], Map.empty)
+  where
+    dg = generaliseDotGraph $ setDirectedness graphToDot nonClusteredParams g
+    info = graphStructureInformation dg
 
 -- -----------------------------------------------------------------------------
 -- Helper utility functions
diff --git a/Data/GraphViz/Types.hs b/Data/GraphViz/Types.hs
--- a/Data/GraphViz/Types.hs
+++ b/Data/GraphViz/Types.hs
@@ -88,6 +88,13 @@
 module Data.GraphViz.Types
     ( -- * Abstraction from the representation type
       DotRepr(..)
+      -- ** Helper types for looking up information within a @DotRepr@.
+    , ClusterLookup
+    , NodeLookup
+    , Path
+      -- ** Obtaining the @DotNode@s and @DotEdges@.
+    , graphNodes
+    , graphEdges
       -- ** Printing and parsing a @DotRepr@.
     , printDotGraph
     , parseDotGraph
@@ -100,21 +107,22 @@
       -- * Sub-components of a @DotGraph@.
     , GraphID(..) -- Re-exported from Data.GraphViz.Types.Common
     , DotStatements(..)
-    , GlobalAttributes(..)
+    , GlobalAttributes(..) -- Re-exported from Data.GraphViz.Types.Common
     , DotSubGraph(..)
     , DotNode(..)
     , DotEdge(..) -- Re-exported from Data.GraphViz.Types.Common
     ) where
 
 import Data.GraphViz.Types.Common
-import Data.GraphViz.Attributes( Attributes, Attribute
-                               , usedByGraphs, usedByClusters, usedBySubGraphs
-                               , usedByNodes, usedByEdges)
+import Data.GraphViz.Types.State
+import Data.GraphViz.Attributes( Attribute
+                               , usedByGraphs, usedByClusters, usedBySubGraphs)
 import Data.GraphViz.Util
 import Data.GraphViz.Parsing
 import Data.GraphViz.PreProcessing
 import Data.GraphViz.Printing
 
+import Control.Arrow((&&&))
 import Control.Monad(liftM)
 
 -- -----------------------------------------------------------------------------
@@ -122,23 +130,46 @@
 -- | This class is used to provide a common interface to different
 --   ways of representing a graph in /Dot/ form.
 class (PrintDot (dg n), ParseDot (dg n)) => DotRepr dg n where
+  -- | Return the ID of the graph.
+  getID :: dg n -> Maybe GraphID
+
   -- | Is this graph directed?
   graphIsDirected :: dg n -> Bool
 
+  -- | Is this graph strict?
+  graphIsStrict :: dg n -> Bool
+
   -- | A strict graph disallows multiple edges.
   makeStrict :: dg n -> dg n
 
   -- | Set the ID of the graph.
   setID :: GraphID -> dg n -> dg n
 
-  -- | Return all the 'DotNode's contained within this 'DotRepr'.
-  --   There is no requirement for it to return implicitly defined
-  --   nodes found only within a 'DotEdge'.
-  graphNodes :: dg n -> [DotNode n]
+  -- | Return information on all the clusters contained within this
+  --   'DotRepr', as well as the top-level 'GraphAttrs' for the
+  --   overall graph.
+  graphStructureInformation :: dg n -> (GlobalAttributes, ClusterLookup)
 
-  -- | Return all the 'DotEdge's contained within this 'DotRepr'.
-  graphEdges :: dg n -> [DotEdge n]
+  -- | Return information on the 'DotNode's contained within this
+  --   'DotRepr'.  The 'Bool' parameter indicates if applicable
+  --   'NodeAttrs' should be included.
+  nodeInformation :: Bool -> dg n -> NodeLookup n
 
+  -- | Return information on the 'DotEdge's contained within this
+  --   'DotRepr'.  The 'Bool' parameter indicates if applicable
+  --   'EdgeAttrs' should be included.
+  edgeInformation :: Bool -> dg n -> [DotEdge n]
+
+-- | Returns all resultant 'DotNode's in the 'DotRepr' (not including
+--   'NodeAttr's).
+graphNodes :: (DotRepr dg n, Ord n) => dg n -> [DotNode n]
+graphNodes = toDotNodes . nodeInformation True
+
+-- | Returns all resultant 'DotEdge's in the 'DotRepr' (not including
+--   'EdgeAttr's).
+graphEdges :: (DotRepr dg n) => dg n -> [DotEdge n]
+graphEdges = edgeInformation True
+
 -- | The actual /Dot/ code for an instance of 'DotRepr'.  Note that it
 --   is expected that @'parseDotGraph' . 'printDotGraph' == 'id'@
 --   (this might not be true the other way around due to un-parseable
@@ -164,17 +195,26 @@
                            }
                   deriving (Eq, Ord, Show, Read)
 
-instance (PrintDot n, ParseDot n) => DotRepr DotGraph n where
+instance (Ord n, PrintDot n, ParseDot n) => DotRepr DotGraph n where
+  getID = graphID
+
   graphIsDirected = directedGraph
 
+  graphIsStrict = strictGraph
+
   makeStrict g = g { strictGraph = True }
 
   setID i g = g { graphID = Just i }
 
-  graphNodes = statementNodes . graphStatements
+  graphStructureInformation = getGraphInfo
+                              . statementStructure . graphStatements
 
-  graphEdges = statementEdges . graphStatements
+  nodeInformation wGlobal = getNodeLookup wGlobal
+                            . statementNodes . graphStatements
 
+  edgeInformation wGlobal = getDotEdges wGlobal
+                            . statementEdges . graphStatements
+
 -- | Check if all the 'Attribute's are being used correctly.
 isValidGraph :: DotGraph a -> Bool
 isValidGraph = null . graphErrors
@@ -200,16 +240,6 @@
 
 -- -----------------------------------------------------------------------------
 
--- | Used to record invalid 'Attribute' usage.  A 'Just' value denotes
---   that it was used in an explicit 'DotNode' or 'DotEdge' usage;
---   'Nothing' means that it was used in a 'GlobalAttributes' value.
-data DotError a = GraphError Attribute
-                | NodeError (Maybe a) Attribute
-                | EdgeError (Maybe (a,a)) Attribute
-                deriving (Eq, Ord, Show, Read)
-
--- -----------------------------------------------------------------------------
-
 data DotStatements a = DotStmts { attrStmts :: [GlobalAttributes]
                                 , subGraphs :: [DotSubGraph a]
                                 , nodeStmts :: [DotNode a]
@@ -252,81 +282,23 @@
                        ++ concatMap invalidNode (nodeStmts stmts)
                        ++ concatMap invalidEdge (edgeStmts stmts)
 
-statementNodes       :: DotStatements a -> [DotNode a]
-statementNodes stmts = concatMap subGraphNodes (subGraphs stmts)
-                       ++ nodeStmts stmts
-
-statementEdges       :: DotStatements a -> [DotEdge a]
-statementEdges stmts = concatMap subGraphEdges (subGraphs stmts)
-                       ++ edgeStmts stmts
-
--- -----------------------------------------------------------------------------
-
--- | Represents a list of top-level list of 'Attribute's for the
---   entire graph/sub-graph.  Note that 'GraphAttrs' also applies to
---   'DotSubGraph's.
---
---   Note that Dot allows a single 'Attribute' to be listen on a line;
---   if this is the case then when parsing, the type of 'Attribute' it
---   is determined and that type of 'GlobalAttribute' is created.
-data GlobalAttributes = GraphAttrs { attrs :: Attributes }
-                      | NodeAttrs  { attrs :: Attributes }
-                      | EdgeAttrs  { attrs :: Attributes }
-                        deriving (Eq, Ord, Show, Read)
-
-instance PrintDot GlobalAttributes where
-    -- Can't use printAttrBased because an empty list still must be printed.
-    unqtDot ga = printGlobAttrType ga <+> toDot (attrs ga) <> semi
-
-    unqtListToDot = printAttrBasedList printGlobAttrType attrs
-
-    listToDot = unqtListToDot
-
-printGlobAttrType              :: GlobalAttributes -> DotCode
-printGlobAttrType GraphAttrs{} = text "graph"
-printGlobAttrType NodeAttrs{}  = text "node"
-printGlobAttrType EdgeAttrs{}  = text "edge"
-
-instance ParseDot GlobalAttributes where
-    -- Not using parseAttrBased here because we want to force usage of
-    -- Attributes.
-    parseUnqt = do gat <- parseGlobAttrType
-                   as <- whitespace' >> parse
-                   return $ gat as
-                `onFail`
-                liftM determineType parse
-
-    parse = parseUnqt -- Don't want the option of quoting
-            `adjustErr`
-            (++ "\n\nNot a valid listing of global attributes")
-
-    -- Have to do this manually because of the special case
-    parseUnqtList = parseStatements parse
-
-    parseList = parseUnqtList
-
-parseGlobAttrType :: Parse (Attributes -> GlobalAttributes)
-parseGlobAttrType = oneOf [ stringRep GraphAttrs "graph"
-                          , stringRep NodeAttrs "node"
-                          , stringRep EdgeAttrs "edge"
-                          ]
+statementStructure :: DotStatements n -> GraphState ()
+statementStructure stmts
+  = do mapM_ addGraphGlobals $ attrStmts stmts
+       mapM_ (withSubGraphID addSubGraph statementStructure) $ subGraphs stmts
 
-determineType :: Attribute -> GlobalAttributes
-determineType attr
-    | usedByGraphs attr   = GraphAttrs attr'
-    | usedByClusters attr = GraphAttrs attr' -- Also covers SubGraph case
-    | usedByNodes attr    = NodeAttrs attr'
-    | otherwise           = EdgeAttrs attr' -- Must be for edges.
-    where
-      attr' = [attr]
+statementNodes :: (Ord a) => DotStatements a -> NodeState a ()
+statementNodes stmts
+  = do mapM_ addNodeGlobals $ attrStmts stmts
+       mapM_ (withSubGraphID recursiveCall statementNodes) $ subGraphs stmts
+       mapM_ addNode $ nodeStmts stmts
+       mapM_ addEdgeNodes $ edgeStmts stmts
 
-invalidGlobal                   :: (Attribute -> Bool) -> GlobalAttributes
-                                   -> [DotError a]
-invalidGlobal f (GraphAttrs as) = map GraphError $ filter (not . f) as
-invalidGlobal _ (NodeAttrs  as) = map (NodeError Nothing)
-                                  $ filter (not . usedByNodes) as
-invalidGlobal _ (EdgeAttrs  as) = map (EdgeError Nothing)
-                                  $ filter (not . usedByEdges) as
+statementEdges :: DotStatements a -> EdgeState a ()
+statementEdges stmts
+  = do mapM_ addEdgeGlobals $ attrStmts stmts
+       mapM_ (withSubGraphID recursiveCall statementEdges) $ subGraphs stmts
+       mapM_ addEdge $ edgeStmts stmts
 
 -- -----------------------------------------------------------------------------
 
@@ -344,7 +316,7 @@
     listToDot = unqtListToDot
 
 printSubGraphID' :: DotSubGraph a -> DotCode
-printSubGraphID' = printSubGraphID (\sg -> (isCluster sg, subGraphID sg))
+printSubGraphID' = printSubGraphID (isCluster &&& subGraphID)
 
 instance (ParseDot a) => ParseDot (DotSubGraph a) where
     parseUnqt = parseStmtBased parseUnqt (parseSubGraphID DotSG)
@@ -368,64 +340,8 @@
     where
       valFunc = bool usedBySubGraphs usedByClusters (isCluster sg)
 
-subGraphNodes :: DotSubGraph a -> [DotNode a]
-subGraphNodes = statementNodes . subGraphStmts
-
-subGraphEdges :: DotSubGraph a -> [DotEdge a]
-subGraphEdges = statementEdges . subGraphStmts
-
--- -----------------------------------------------------------------------------
-
--- | A node in 'DotGraph'.
-data DotNode a = DotNode { nodeID :: a
-                         , nodeAttributes :: Attributes
-                         }
-                 deriving (Eq, Ord, Show, Read)
-
-instance (PrintDot a) => PrintDot (DotNode a) where
-    unqtDot = printAttrBased printNodeID nodeAttributes
-
-    unqtListToDot = printAttrBasedList printNodeID nodeAttributes
-
-    listToDot = unqtListToDot
-
-printNodeID :: (PrintDot a) => DotNode a -> DotCode
-printNodeID = toDot . nodeID
-
-instance (ParseDot a) => ParseDot (DotNode a) where
-    parseUnqt = parseAttrBased parseNodeID
-
-    parse = parseUnqt -- Don't want the option of quoting
-
-    parseUnqtList = parseAttrBasedList parseNodeID
-
-    parseList = parseUnqtList
-
-parseNodeID :: (ParseDot a) => Parse (Attributes -> DotNode a)
-parseNodeID = liftM DotNode parseAndCheck
+withSubGraphID        :: (Maybe (Maybe GraphID) -> b -> a)
+                         -> (DotStatements n -> b) -> DotSubGraph n -> a
+withSubGraphID f g sg = f mid . g $ subGraphStmts sg
   where
-    parseAndCheck = do a <- parse
-                       me <- optional parseUnwanted
-                       maybe (return a) (const notANode) me
-    notANode = fail "This appears to be an edge, not a node"
-    parseUnwanted = oneOf [ parseEdgeType >> return ()
-                          , character ':' >> return () -- PortPos value
-                          ]
-
-instance Functor DotNode where
-    fmap f n = n { nodeID = f $ nodeID n }
-
-invalidNode   :: DotNode a -> [DotError a]
-invalidNode n = map (NodeError (Just $ nodeID n))
-                $ filter (not . usedByNodes) (nodeAttributes n)
-
--- -----------------------------------------------------------------------------
-
--- Defined here rather than in Common with the rest of the Edge stuff
--- because all the DotError stuff is here.
-
-invalidEdge   :: DotEdge a -> [DotError a]
-invalidEdge e = map (EdgeError eID)
-                $ filter (not . usedByEdges) (edgeAttributes e)
-    where
-      eID = Just (edgeFromNodeID e, edgeToNodeID e)
+    mid = bool Nothing (Just $ subGraphID sg) $ isCluster sg
diff --git a/Data/GraphViz/Types/Clustering.hs b/Data/GraphViz/Types/Clustering.hs
--- a/Data/GraphViz/Types/Clustering.hs
+++ b/Data/GraphViz/Types/Clustering.hs
@@ -10,7 +10,8 @@
    This module defines types for creating clusters.
 -}
 module Data.GraphViz.Types.Clustering
-    ( NodeCluster(..)
+    ( LNodeCluster
+    , NodeCluster(..)
     , clustersToNodes
     ) where
 
@@ -23,16 +24,20 @@
 
 -- -----------------------------------------------------------------------------
 
+-- | A type alias for 'NodeCluster' that specifies that the node value
+--   is an 'LNode'.
+type LNodeCluster c a = NodeCluster c (LNode a)
+
 -- | Define into which cluster a particular node belongs.
 --   Clusters can be nested to arbitrary depth.
-data NodeCluster c a = N (LNode a) -- ^ Indicates the actual Node in the Graph.
+data NodeCluster c a = N a -- ^ Indicates the actual Node in the Graph.
                      | C c (NodeCluster c a) -- ^ Indicates that the
                                              --   'NodeCluster' is in
                                              --   the Cluster /c/.
                         deriving (Show)
 
 -- | Create the /Dot/ representation for the given graph.
-clustersToNodes :: (Ord c, Graph gr) => (LNode a -> NodeCluster c l)
+clustersToNodes :: (Ord c, Graph gr) => (LNode a -> NodeCluster c (LNode l))
                    -> (c -> Maybe GraphID) -> (c -> [GlobalAttributes])
                    -> (LNode l -> Attributes) -> gr a b
                    -> ([DotSubGraph Node], [DotNode Node])
@@ -45,7 +50,7 @@
 -- -----------------------------------------------------------------------------
 
 -- | A tree representation of a cluster.
-data ClusterTree c a = NT (LNode a)
+data ClusterTree c a = NT a
                      | CT c [ClusterTree c a]
                        deriving (Show)
 
@@ -80,14 +85,14 @@
                   . sortBy clustOrder
     where
       grpCls []              = []
-      grpCls ns@((NT _):_)   = ns
-      grpCls cs@((CT c _):_) = [CT c (collapseNClusts $ concatMap getNodes cs)]
+      grpCls ns@(NT _ : _)   = ns
+      grpCls cs@(CT c _ : _) = [CT c (collapseNClusts $ concatMap getNodes cs)]
 
 -- | Convert the cluster representation of the trees into 'DotNode's
 --   and 'DotSubGraph's (with @'isCluster' = 'True'@, and
 --   @'subGraphID' = 'Nothing'@).
 treesToDot :: (c -> Maybe GraphID) -> (c -> [GlobalAttributes])
-              -> (LNode a -> Attributes) -> [ClusterTree c a]
+              -> (LNode a -> Attributes) -> [ClusterTree c (LNode a)]
               -> ([DotSubGraph Node], [DotNode Node])
 treesToDot cID fmtCluster fmtNode
     = partitionEithers
@@ -95,7 +100,7 @@
 
 -- | Convert this 'ClusterTree' into its /Dot/ representation.
 treeToDot :: (c -> Maybe GraphID) -> (c -> [GlobalAttributes])
-             -> (LNode a -> Attributes) -> ClusterTree c a
+             -> (LNode a -> Attributes) -> ClusterTree c (LNode a)
              -> Either (DotSubGraph Node) (DotNode Node)
 treeToDot _ _ fmtNode (NT ln)
     = Right DotNode { nodeID         = fst ln
diff --git a/Data/GraphViz/Types/Common.hs b/Data/GraphViz/Types/Common.hs
--- a/Data/GraphViz/Types/Common.hs
+++ b/Data/GraphViz/Types/Common.hs
@@ -15,7 +15,9 @@
 import Data.GraphViz.Parsing
 import Data.GraphViz.Printing
 import Data.GraphViz.Util
-import Data.GraphViz.Attributes(Attributes, Attribute(HeadPort, TailPort))
+import Data.GraphViz.Attributes( Attributes, Attribute(HeadPort, TailPort)
+                               , usedByGraphs, usedByClusters
+                               , usedByNodes, usedByEdges)
 import Data.GraphViz.Attributes.Internal(PortPos, parseEdgeBasedPP)
 
 import Data.Maybe(isJust)
@@ -55,9 +57,133 @@
                then Dbl $ toDouble str
                else Str str
 
+-- -----------------------------------------------------------------------------
 
+-- | Used to record invalid 'Attribute' usage.  A 'Just' value denotes
+--   that it was used in an explicit 'DotNode' or 'DotEdge' usage;
+--   'Nothing' means that it was used in a 'GlobalAttributes' value.
+data DotError a = GraphError Attribute
+                | NodeError (Maybe a) Attribute
+                | EdgeError (Maybe (a,a)) Attribute
+                deriving (Eq, Ord, Show, Read)
+
 -- -----------------------------------------------------------------------------
 
+-- Re-exported by Data.GraphViz.Types and Data.GraphViz.Types.Generalised
+
+-- | Represents a list of top-level list of 'Attribute's for the
+--   entire graph/sub-graph.  Note that 'GraphAttrs' also applies to
+--   'DotSubGraph's.
+--
+--   Note that Dot allows a single 'Attribute' to be listen on a line;
+--   if this is the case then when parsing, the type of 'Attribute' it
+--   is determined and that type of 'GlobalAttribute' is created.
+data GlobalAttributes = GraphAttrs { attrs :: Attributes }
+                      | NodeAttrs  { attrs :: Attributes }
+                      | EdgeAttrs  { attrs :: Attributes }
+                        deriving (Eq, Ord, Show, Read)
+
+instance PrintDot GlobalAttributes where
+    -- Can't use printAttrBased because an empty list still must be printed.
+    unqtDot ga = printGlobAttrType ga <+> toDot (attrs ga) <> semi
+
+    unqtListToDot = printAttrBasedList printGlobAttrType attrs
+
+    listToDot = unqtListToDot
+
+printGlobAttrType              :: GlobalAttributes -> DotCode
+printGlobAttrType GraphAttrs{} = text "graph"
+printGlobAttrType NodeAttrs{}  = text "node"
+printGlobAttrType EdgeAttrs{}  = text "edge"
+
+instance ParseDot GlobalAttributes where
+    -- Not using parseAttrBased here because we want to force usage of
+    -- Attributes.
+    parseUnqt = do gat <- parseGlobAttrType
+                   as <- whitespace' >> parse
+                   return $ gat as
+                `onFail`
+                liftM determineType parse
+
+    parse = parseUnqt -- Don't want the option of quoting
+            `adjustErr`
+            (++ "\n\nNot a valid listing of global attributes")
+
+    -- Have to do this manually because of the special case
+    parseUnqtList = parseStatements parse
+
+    parseList = parseUnqtList
+
+parseGlobAttrType :: Parse (Attributes -> GlobalAttributes)
+parseGlobAttrType = oneOf [ stringRep GraphAttrs "graph"
+                          , stringRep NodeAttrs "node"
+                          , stringRep EdgeAttrs "edge"
+                          ]
+
+determineType :: Attribute -> GlobalAttributes
+determineType attr
+    | usedByGraphs attr   = GraphAttrs attr'
+    | usedByClusters attr = GraphAttrs attr' -- Also covers SubGraph case
+    | usedByNodes attr    = NodeAttrs attr'
+    | otherwise           = EdgeAttrs attr' -- Must be for edges.
+    where
+      attr' = [attr]
+
+invalidGlobal                   :: (Attribute -> Bool) -> GlobalAttributes
+                                   -> [DotError a]
+invalidGlobal f (GraphAttrs as) = map GraphError $ filter (not . f) as
+invalidGlobal _ (NodeAttrs  as) = map (NodeError Nothing)
+                                  $ filter (not . usedByNodes) as
+invalidGlobal _ (EdgeAttrs  as) = map (EdgeError Nothing)
+                                  $ filter (not . usedByEdges) as
+
+-- -----------------------------------------------------------------------------
+
+-- | A node in 'DotGraph'.
+data DotNode a = DotNode { nodeID :: a
+                         , nodeAttributes :: Attributes
+                         }
+                 deriving (Eq, Ord, Show, Read)
+
+instance (PrintDot a) => PrintDot (DotNode a) where
+    unqtDot = printAttrBased printNodeID nodeAttributes
+
+    unqtListToDot = printAttrBasedList printNodeID nodeAttributes
+
+    listToDot = unqtListToDot
+
+printNodeID :: (PrintDot a) => DotNode a -> DotCode
+printNodeID = toDot . nodeID
+
+instance (ParseDot a) => ParseDot (DotNode a) where
+    parseUnqt = parseAttrBased parseNodeID
+
+    parse = parseUnqt -- Don't want the option of quoting
+
+    parseUnqtList = parseAttrBasedList parseNodeID
+
+    parseList = parseUnqtList
+
+parseNodeID :: (ParseDot a) => Parse (Attributes -> DotNode a)
+parseNodeID = liftM DotNode parseAndCheck
+  where
+    parseAndCheck = do a <- parse
+                       me <- optional parseUnwanted
+                       maybe (return a) (const notANode) me
+    notANode = fail "This appears to be an edge, not a node"
+    parseUnwanted = oneOf [ parseEdgeType >> return ()
+                          , character ':' >> return () -- PortPos value
+                          ]
+
+instance Functor DotNode where
+    fmap f n = n { nodeID = f $ nodeID n }
+
+invalidNode   :: DotNode a -> [DotError a]
+invalidNode n = map (NodeError (Just $ nodeID n))
+                $ filter (not . usedByNodes) (nodeAttributes n)
+
+-- -----------------------------------------------------------------------------
+
 -- This is re-exported in Data.GraphViz.Types; defined here so that
 -- Generalised can access and use parseEdgeLine (needed for "a -> b ->
 -- c"-style edge statements).
@@ -163,6 +289,12 @@
 
 undirEdge' :: DotCode
 undirEdge' = text undirEdge
+
+invalidEdge   :: DotEdge a -> [DotError a]
+invalidEdge e = map (EdgeError eID)
+                $ filter (not . usedByEdges) (edgeAttributes e)
+    where
+      eID = Just (edgeFromNodeID e, edgeToNodeID e)
 
 -- -----------------------------------------------------------------------------
 -- Labels
diff --git a/Data/GraphViz/Types/Generalised.hs b/Data/GraphViz/Types/Generalised.hs
--- a/Data/GraphViz/Types/Generalised.hs
+++ b/Data/GraphViz/Types/Generalised.hs
@@ -36,14 +36,19 @@
        , generaliseDotGraph
        ) where
 
-import Data.GraphViz.Types hiding (GraphID(..), DotEdge(..))
+import Data.GraphViz.Types hiding ( GraphID(..)
+                                  , GlobalAttributes(..)
+                                  , DotEdge(..))
 import Data.GraphViz.Types.Common
+import Data.GraphViz.Types.State
 import Data.GraphViz.Parsing
 import Data.GraphViz.Printing
+import Data.GraphViz.Util(bool)
 
 import qualified Data.Sequence as Seq
 import Data.Sequence(Seq, (><))
 import qualified Data.Foldable as F
+import Control.Arrow((&&&))
 import Control.Monad(liftM)
 
 -- -----------------------------------------------------------------------------
@@ -56,17 +61,26 @@
                              }
                  deriving (Eq, Ord, Show, Read)
 
-instance (PrintDot n, ParseDot n) => DotRepr GDotGraph n where
+instance (Ord n, PrintDot n, ParseDot n) => DotRepr GDotGraph n where
+  getID = gGraphID
+
   graphIsDirected = gDirectedGraph
 
+  graphIsStrict = gStrictGraph
+
   makeStrict g = g { gStrictGraph = True }
 
   setID i g = g { gGraphID = Just i }
 
-  graphNodes = statementNodes . gGraphStatements
+  graphStructureInformation = getGraphInfo
+                              . statementStructure . gGraphStatements
 
-  graphEdges = statementEdges . gGraphStatements
+  nodeInformation wGlobal = getNodeLookup wGlobal
+                            . statementNodes . gGraphStatements
 
+  edgeInformation wGlobal = getDotEdges wGlobal
+                            . statementEdges . gGraphStatements
+
 instance (PrintDot a) => PrintDot (GDotGraph a) where
   unqtDot = printStmtBased printGraphID' gGraphStatements printGStmts
     where
@@ -102,12 +116,15 @@
 parseGStmts :: (ParseDot a) => Parse (GDotStatements a)
 parseGStmts = liftM Seq.fromList parse
 
-statementNodes :: GDotStatements a -> [DotNode a]
-statementNodes = concatMap stmtNodes . F.toList
+statementStructure :: GDotStatements a -> GraphState ()
+statementStructure = F.mapM_ stmtStructure
 
-statementEdges :: GDotStatements a -> [DotEdge a]
-statementEdges = concatMap stmtEdges . F.toList
+statementNodes :: (Ord a) => GDotStatements a -> NodeState a ()
+statementNodes = F.mapM_ stmtNodes
 
+statementEdges :: GDotStatements a -> EdgeState a ()
+statementEdges = F.mapM_ stmtEdges
+
 generaliseStatements       :: DotStatements a -> GDotStatements a
 generaliseStatements stmts = atts >< sgs >< ns >< es
   where
@@ -161,16 +178,23 @@
   fmap f (DN dn) = DN $ fmap f dn
   fmap f (DE de) = DE $ fmap f de
 
-stmtNodes         :: GDotStatement a -> [DotNode a]
-stmtNodes (SG sg) = subGraphNodes sg
-stmtNodes (DN dn) = [dn]
-stmtNodes _       = []
+stmtStructure         :: GDotStatement n -> GraphState ()
+stmtStructure (GA ga) = addGraphGlobals ga
+stmtStructure (SG sg) = withSubGraphID addSubGraph statementStructure sg
+stmtStructure _       = return ()
 
-stmtEdges         :: (GDotStatement a) -> [DotEdge a]
-stmtEdges (SG sg) = subGraphEdges sg
-stmtEdges (DE de) = [de]
-stmtEdges _       = []
+stmtNodes         :: (Ord a) => GDotStatement a -> NodeState a ()
+stmtNodes (GA ga) = addNodeGlobals ga
+stmtNodes (SG sg) = withSubGraphID recursiveCall statementNodes sg
+stmtNodes (DN dn) = addNode dn
+stmtNodes (DE de) = addEdgeNodes de
 
+stmtEdges         :: GDotStatement a -> EdgeState a ()
+stmtEdges (GA ga) = addEdgeGlobals ga
+stmtEdges (SG sg) = withSubGraphID recursiveCall statementEdges sg
+stmtEdges (DE de) = addEdge de
+stmtEdges _       = return ()
+
 -- -----------------------------------------------------------------------------
 
 data GDotSubGraph a = GDotSG { gIsCluster     :: Bool
@@ -187,7 +211,7 @@
   listToDot = unqtListToDot
 
 printSubGraphID' :: GDotSubGraph a -> DotCode
-printSubGraphID' = printSubGraphID (\sg -> (gIsCluster sg, gSubGraphID sg))
+printSubGraphID' = printSubGraphID (gIsCluster &&& gSubGraphID)
 
 instance (ParseDot a) => ParseDot (GDotSubGraph a) where
   parseUnqt = parseStmtBased parseGStmts (parseSubGraphID GDotSG)
@@ -206,12 +230,6 @@
 instance Functor GDotSubGraph where
     fmap f sg = sg { gSubGraphStmts = (fmap . fmap) f $ gSubGraphStmts sg }
 
-subGraphNodes :: GDotSubGraph a -> [DotNode a]
-subGraphNodes = statementNodes . gSubGraphStmts
-
-subGraphEdges :: GDotSubGraph a -> [DotEdge a]
-subGraphEdges = statementEdges . gSubGraphStmts
-
 generaliseSubGraph                       :: DotSubGraph a -> GDotSubGraph a
 generaliseSubGraph (DotSG isC mID stmts) = GDotSG { gIsCluster     = isC
                                                   , gSubGraphID    = mID
@@ -219,3 +237,9 @@
                                                   }
   where
     stmts' = generaliseStatements stmts
+
+withSubGraphID        :: (Maybe (Maybe GraphID) -> b -> a)
+                         -> (GDotStatements n -> b) -> GDotSubGraph n -> a
+withSubGraphID f g sg = f mid . g $ gSubGraphStmts sg
+  where
+    mid = bool Nothing (Just $ gSubGraphID sg) $ gIsCluster sg
diff --git a/Data/GraphViz/Types/State.hs b/Data/GraphViz/Types/State.hs
new file mode 100644
--- /dev/null
+++ b/Data/GraphViz/Types/State.hs
@@ -0,0 +1,283 @@
+{-# OPTIONS_HADDOCK hide #-}
+
+{- |
+   Module      : Data.GraphViz.Types.State
+   Description : Create lookups for 'Attribute's.
+   Copyright   : (c) Ivan Lazar Miljenovic
+   License     : 3-Clause BSD-style
+   Maintainer  : Ivan.Miljenovic@gmail.com
+
+   This module provides functions to assist with building 'Attribute'
+   lookups.
+-}
+module Data.GraphViz.Types.State
+       ( Path
+       , recursiveCall
+         --
+       , GraphState
+       , ClusterLookup
+       , getGraphInfo
+       , addSubGraph
+       , addGraphGlobals
+         --
+       , NodeState
+       , NodeLookup
+       , getNodeLookup
+       , toDotNodes
+       , addNodeGlobals
+       , addNode
+       , addEdgeNodes
+         --
+       , EdgeState
+       , getDotEdges
+       , addEdgeGlobals
+       , addEdge
+       ) where
+
+import Data.GraphViz.Types.Common
+import Data.GraphViz.Attributes(Attributes, Attribute, sameAttribute)
+
+import Data.Function(on)
+import qualified Data.Map as Map
+import Data.Map(Map)
+import qualified Data.Set as Set
+import Data.Set(Set)
+import qualified Data.Sequence as Seq
+import Data.Sequence(Seq, (|>), ViewL(..))
+import Control.Arrow((&&&), (***))
+import Control.Monad(when)
+import Control.Monad.Trans.State
+
+-- -----------------------------------------------------------------------------
+
+type GVState s a = State (StateValue s) a
+
+data StateValue a = SV { globalAttrs :: SAttrs
+                       , useGlobals  :: Bool
+                       , globalPath  :: Path
+                       , value       :: a
+                       }
+                  deriving (Eq, Ord, Show, Read)
+
+-- | The path of clusters that must be traversed to reach this spot.
+type Path = Seq (Maybe GraphID)
+
+modifyGlobal   :: (SAttrs -> SAttrs) -> GVState s ()
+modifyGlobal f = modify f'
+  where
+    f' sv@(SV{globalAttrs = gas}) = sv{globalAttrs = f gas}
+
+modifyValue   :: (s -> s) -> GVState s ()
+modifyValue f = modify f'
+  where
+    f' sv@(SV{value = s}) = sv{value = f s}
+
+addGlobals    :: Attributes -> GVState s ()
+addGlobals as = do addG <- gets useGlobals
+                   when addG $ modifyGlobal (`unionWith` as)
+
+getGlobals :: GVState s SAttrs
+getGlobals = gets globalAttrs
+
+getPath :: GVState s Path
+getPath = gets globalPath
+
+modifyPath   :: (Path -> Path) -> GVState s ()
+modifyPath f = modify f'
+  where
+    f' sv@(SV{globalPath = p}) = sv{globalPath = f p}
+
+-- When calling recursively, back-up and restore the global attrs
+-- since they shouldn't change.
+--
+-- Outer Maybe: Nothing for subgraphs, Just for clusters
+recursiveCall      :: Maybe (Maybe GraphID) -> GVState s () -> GVState s ()
+recursiveCall mc s = do gas <- getGlobals
+                        p   <- getPath
+                        maybe (return ()) (modifyPath . flip (|>)) mc
+                        s
+                        modifyGlobal (const gas)
+                        modifyPath (const p)
+
+-- -----------------------------------------------------------------------------
+
+-- | Defined as a wrapper around 'Attribute' where equality is based
+--   solely upon the constructor, not the contents.
+newtype SameAttr = SA { getAttr :: Attribute }
+                 deriving (Show, Read)
+
+instance Eq SameAttr where
+  (==) = sameAttribute `on` getAttr
+
+instance Ord SameAttr where
+  compare sa1 sa2
+    | sa1 == sa2 = EQ
+    | otherwise  = (compare `on` getAttr) sa1 sa2
+
+type SAttrs = Set SameAttr
+
+toSAttr :: Attributes -> SAttrs
+toSAttr = Set.fromList . map SA
+
+unionWith        :: SAttrs -> Attributes -> SAttrs
+unionWith sas as = Set.fromList (map SA as) `Set.union` sas
+
+unSame :: SAttrs -> Attributes
+unSame = map getAttr . Set.toList
+
+-- -----------------------------------------------------------------------------
+-- Dealing with sub-graphs
+
+type GraphState a = GVState ClusterLookup' a
+
+-- | The available information for each cluster; the @['Path']@
+--   denotes all locations where that particular cluster is located
+--   (more than one location can indicate possible problems).
+type ClusterLookup = Map (Maybe GraphID) ([Path], GlobalAttributes)
+
+type ClusterLookup' = Map (Maybe GraphID) ClusterInfo
+
+type ClusterInfo = (DList Path, SAttrs)
+
+getGraphInfo :: GraphState a -> (GlobalAttributes, ClusterLookup)
+getGraphInfo = (toGlobal . (globalAttrs &&& convert) . value)
+               . flip execState initState
+  where
+    convert = Map.map ((uniq . toList) *** toGlobal)
+    toGlobal = GraphAttrs . unSame
+    initState = SV Set.empty True Seq.empty Map.empty
+    uniq = Set.toList . Set.fromList
+
+mergeCInfos          :: ClusterInfo -> ClusterInfo -> ClusterInfo
+mergeCInfos (p1,as1) = append p1 *** Set.union as1
+
+addCluster                 :: Maybe (Maybe GraphID) -> Path -> SAttrs
+                              -> GraphState ()
+addCluster Nothing    _ _  = return ()
+addCluster (Just gid) p as = modifyValue $ Map.insertWith mergeCInfos gid ci
+  where
+    ci = (singleton p, as)
+
+-- Use this instead of recursiveCall
+addSubGraph           :: Maybe (Maybe GraphID) -> GraphState a -> GraphState ()
+addSubGraph mid cntns = do pth <- getPath -- Want path before we add it...
+                           recursiveCall mid $ do cntns
+                                                  -- But want attrs after we
+                                                  -- finish it.
+                                                  gas <- getGlobals
+                                                  addCluster mid pth gas
+
+addGraphGlobals                 :: GlobalAttributes -> GraphState ()
+addGraphGlobals (GraphAttrs as) = addGlobals as
+addGraphGlobals _               = return ()
+
+-- -----------------------------------------------------------------------------
+-- Dealing with DotNodes
+
+-- | The available information on each 'DotNode' (both explicit and implicit).
+type NodeLookup n = Map n (Path, Attributes)
+
+type NodeLookup' n = Map n NodeInfo
+
+data NodeInfo = NI { atts :: SAttrs
+                   , gAtts :: SAttrs -- from globals
+                   , location :: Path
+                   }
+              deriving (Eq, Ord, Show, Read)
+
+type NodeState n a = GVState (NodeLookup' n) a
+
+toDotNodes :: (Ord n) => NodeLookup n -> [DotNode n]
+toDotNodes = map (\(n,(_,as)) -> DotNode n as) . Map.assocs
+
+getNodeLookup       :: (Ord n) => Bool -> NodeState n a -> NodeLookup n
+getNodeLookup addGs = Map.map combine . value . flip execState initState
+  where
+    initState = SV Set.empty addGs Seq.empty Map.empty
+    combine ni = (location ni, unSame $ atts ni `Set.union` gAtts ni)
+
+-- New -> Old -> Inserted
+--
+-- For specific attributes, newer one takes precedence; for global
+-- attributes and path, older one takes precedence.
+mergeNInfos :: NodeInfo -> NodeInfo -> NodeInfo
+mergeNInfos (NI a1 ga1 p1) (NI a2 ga2 p2) = NI (a1 `Set.union` a2)
+                                                -- old one takes precendence
+                                               (ga2 `Set.union` ga1)
+                                                -- old one takes precendence
+                                               (mergePs p2 p1)
+
+-- | If one 'Path' is a prefix of another, then take the longer one;
+--   otherwise, take the first 'Path'.
+mergePs :: Path -> Path -> Path
+mergePs p1 p2 = mrg' p1 p2
+  where
+    mrg' = mrg `on` Seq.viewl
+    mrg EmptyL      _           = p2
+    mrg _           EmptyL      = p1
+    mrg (c1 :< p1') (c2 :< p2')
+      | c1 == c2                = mrg' p1' p2'
+      | otherwise               = p1
+
+addNodeGlobals                :: GlobalAttributes -> NodeState n ()
+addNodeGlobals (NodeAttrs as) = addGlobals as
+addNodeGlobals _              = return ()
+
+mergeNode            :: (Ord n) => n -> Attributes -> SAttrs -> Path
+                        -> NodeState n ()
+mergeNode n as gas p = modifyValue $ Map.insertWith mergeNInfos n ni
+  where
+    ni = NI (toSAttr as) gas p
+
+addNode                :: (Ord n) => DotNode n -> NodeState n ()
+addNode (DotNode n as) = do gas <- getGlobals
+                            p <- getPath
+                            -- insertWith takes func (new -> old -> inserted)
+                            mergeNode n as gas p
+
+addEdgeNodes :: (Ord n) => DotEdge n -> NodeState n ()
+addEdgeNodes (DotEdge f t _ _) = do gas <- getGlobals
+                                    p <- getPath
+                                    addEN f gas p
+                                    addEN t gas p
+  where
+    addEN n = mergeNode n []
+
+-- -----------------------------------------------------------------------------
+-- Dealing with DotEdges
+
+type EdgeState n a = GVState (DList (DotEdge n)) a
+
+getDotEdges       :: Bool -> EdgeState n a -> [DotEdge n]
+getDotEdges addGs = toList . value . flip execState initState
+  where
+    initState = SV Set.empty addGs Seq.empty empty
+
+addEdgeGlobals                :: GlobalAttributes -> EdgeState n ()
+addEdgeGlobals (EdgeAttrs as) = addGlobals as
+addEdgeGlobals _              = return ()
+
+addEdge :: DotEdge n -> EdgeState n ()
+addEdge de@DotEdge{edgeAttributes = as}
+  = do gas <- getGlobals
+       let de' = de { edgeAttributes = unSame $ unionWith gas as }
+       modifyValue $ snoc de'
+
+-- -----------------------------------------------------------------------------
+
+type DList a = [a] -> [a]
+
+snoc :: a -> DList a -> DList a
+snoc a da = da . (a:)
+
+empty :: DList a
+empty = id
+
+toList :: DList a -> [a]
+toList = ($[])
+
+append       :: DList a -> DList a -> DList a
+append xs ys = xs . ys
+
+singleton :: a -> DList a
+singleton = (:)
diff --git a/FAQ b/FAQ
--- a/FAQ
+++ b/FAQ
@@ -120,7 +120,7 @@
   `layerRange` and `layerList` values; this will be fixed in a future
   release (when state-based printing and parsing is implemented).
 
-* The `/ssss/yyyy` and `//yyyy` forms of printing and prsing `color`s
+* The `/ssss/yyyy` and `//yyyy` forms of printing and parsing `color`s
   are not yet available.
 
 #### Available items of note ####
diff --git a/RunTests.hs b/RunTests.hs
--- a/RunTests.hs
+++ b/RunTests.hs
@@ -10,7 +10,78 @@
 -}
 module Main where
 
-import Data.GraphViz.Testing(runDefaultTests)
+import Data.GraphViz.Testing( Test(name, lookupName)
+                            , defaultTests, runChosenTests)
 
+import Data.Char(toLower)
+import Data.List(maximum)
+import Data.Maybe(mapMaybe)
+import qualified Data.Map as Map
+import Data.Map(Map)
+import Control.Arrow((&&&))
+import Control.Monad(when)
+import System.Environment(getArgs, getProgName)
+import System.Exit(ExitCode(ExitSuccess), exitWith)
+
+-- -----------------------------------------------------------------------------
+
 main :: IO ()
-main = runDefaultTests
+main = do opts <- getArgs
+          let opts' = map (map toLower) opts
+              hasArg arg = any (arg==) opts'
+          when (hasArg "help") helpMsg
+          let tests = if hasArg "all"
+                      then defaultTests
+                      else mapMaybe getTest opts'
+              tests' = if null tests
+                       then defaultTests
+                       else tests
+          runChosenTests tests'
+
+testLookup :: Map String Test
+testLookup = Map.fromList
+             $ map (lookupName &&& id) defaultTests
+
+getTest :: String -> Maybe Test
+getTest = (`Map.lookup` testLookup)
+
+helpMsg :: IO ()
+helpMsg = getProgName >>= (putStr . msg) >> exitWith ExitSuccess
+  where
+    msg nm = unlines
+      [ "This utility is the test-suite for the graphviz library for Haskell."
+      , "Various tests are available; see the table below for a complete list."
+      , "There are several ways of running this program:"
+      , ""
+      , "    " ++ nm ++ "               Run all of the tests"
+      , "    " ++ nm ++ " all           Run all of the tests"
+      , "    " ++ nm ++ " help          Get this help message"
+      , "    " ++ nm ++ " <key>         Run the test associated with each key,"
+      , "        (where <key> denotes a space-separated list of keys"
+      , "         from the table below)."
+      , ""
+      , "The entire test suite takes approximately 50 minutes to run on a"
+      , "2 GHz Mobile Core 2 Duo (running with a single thread)."
+      , ""
+      , helpTable
+      ]
+
+helpTable :: String
+helpTable = unlines $ fmtName ((lnHeader,lnHeaderLen),(nHeader,nHeaderLen))
+                      : line
+                      : map fmtName testNames
+  where
+    andLen = ((id &&& length) .)
+    testNames = map (andLen lookupName &&& andLen name) defaultTests
+    fmtName ((ln,lnl),(n,nl)) = concat [ ln
+                                       , replicate (maxLN-lnl+spacerLen) ' '
+                                       , n
+                                       ]
+    line = replicate (maxLN + spacerLen + maxN) '-'
+    maxLN = maximum $ map (snd . fst) testNames
+    maxN = maximum $ map (snd . snd) testNames
+    spacerLen = 3
+    lnHeader = "Key"
+    lnHeaderLen = length lnHeader
+    nHeader = "Description"
+    nHeaderLen = length nHeader
diff --git a/TestParsing.hs b/TestParsing.hs
deleted file mode 100644
--- a/TestParsing.hs
+++ /dev/null
@@ -1,94 +0,0 @@
-#!/usr/bin/runhaskell
-
-{- |
-   Module      : TestParsing
-   Description : Check if the graphviz parser can parse "real world" Dot code.
-   Copyright   : (c) Ivan Lazar Miljenovic
-   License     : 3-Clause BSD-style
-   Maintainer  : Ivan.Miljenovic@gmail.com
-
-   This module defines a program that determines if the provided files
-   containing Dot code can be properly parsed using graphviz's parsers
-   (with the assumption that the provided code is valid).
-
--}
-module Main where
-
-import Data.GraphViz
-import Data.GraphViz.Types.Generalised
-import Data.GraphViz.Parsing(runParser, parse, discard, allWhitespace', eof)
-import Data.GraphViz.PreProcessing(preProcess)
-
-import Control.Exception(try, ErrorCall(..), IOException)
-import Control.Monad(liftM)
-import System.Environment(getArgs)
-import System.IO(openFile, IOMode(ReadMode))
-
--- -----------------------------------------------------------------------------
-
-main :: IO ()
-main = tryParsing =<< getArgs
-  where
-    tryParsing [] = putStrLn "Test that the graphviz library can parse\
-                             \ \"real life\" Dot code by passing a list\n\
-                             \of files in which contain Dot graphs.\n\
-                             \\n\
-                             \One way of using this file:\n\t\
-                             \$ locate -r \".*\\.\\(gv\\|dot\\)$\" -0\
-                             \ | xargs -0 runhaskell TestParsing.hs"
-    tryParsing fs = mapM_ tryParseFile fs
-
--- -----------------------------------------------------------------------------
-
-
-withParse :: (DotRepr dg n) => (a -> IO String) -> (dg n -> IO ())
-             -> (ErrMsg -> String) -> a -> IO ()
-withParse toStr withDG cmbErr a = do dc <- toStr a
-                                     edg <- tryParse dc
-                                     case edg of
-                                       (Right dg) -> withDG dg
-                                       (Left err) -> do putStrLn "Parsing problem!"
-                                                        putStrLn $ cmbErr err
-                                                        putStrLn  ""
-
-type DG = DotGraph String
-type GDG = GDotGraph String
-type ErrMsg = String
-
-tryParseFile    :: FilePath -> IO ()
-tryParseFile fp = readFile' fp >>= maybeParse
-  where
-    maybeParse (Left err)  = putStrLn $ "Error parsing \"" ++ fp ++ "\":\n\t"
-                                        ++ err ++ "\n"
-    maybeParse (Right dot) = withParse (const $ return dot)
-                                       (tryParseCanon fp)
-                                       (\ e -> fp ++ ": Cannot parse as a GDotGraph:\n"
-                                               ++ e)
-                                       fp
-
-
-tryParseCanon    :: FilePath -> GDG -> IO ()
-tryParseCanon fp = withParse prettyPrint
-                             (const (return ()) . asDG)
-                             (\ e -> fp ++ ": Canonical Form not a DotGraph:\n"
-                                     ++ e)
-  where
-    asDG = flip asTypeOf emptDG
-    emptDG = DotGraph False False Nothing $ DotStmts [] [] [] [] :: DG
-
-tryParse    :: (DotRepr dg n) => String -> IO (Either ErrMsg (dg n))
-tryParse dc = liftM getErrMsg . try
-              $ let (dg, rst) = runParser parse $ preProcess dc
-                in length rst `seq` return dg
-
-getErrMsg :: Either ErrorCall a -> Either ErrMsg a
-getErrMsg = either getEC Right
-  where
-    getEC (ErrorCall e) = Left e
-
-readFile'    :: FilePath -> IO (Either ErrMsg String)
-readFile' fp = liftM getMsg . try
-               $ openFile fp ReadMode >>= hGetContents'
-  where
-    getMsg :: Either IOException String -> Either ErrMsg String
-    getMsg = either (Left . show) Right
diff --git a/graphviz.cabal b/graphviz.cabal
--- a/graphviz.cabal
+++ b/graphviz.cabal
@@ -1,5 +1,5 @@
 Name:               graphviz
-Version:            2999.9.0.0
+Version:            2999.10.0.0
 Stability:          Beta
 Synopsis:           Graphviz bindings for Haskell.
 Description: {
@@ -43,8 +43,8 @@
                     Changelog
                     README
                     FAQ
-                    Data/GraphViz/AttributeGenerator.hs
-                    TestParsing.hs
+                    utils/AttributeGenerator.hs
+                    utils/TestParsing.hs
 
 Source-Repository head
     Type:         darcs
@@ -59,12 +59,13 @@
                            extensible-exceptions,
                            containers,
                            process,
-                           fgl,
+                           fgl == 5.4.*,
                            filepath,
-                           polyparse >= 1.4,
+                           polyparse == 1.4.*,
                            pretty,
                            bytestring < 0.10,
-                           colour >= 2.3 && < 2.4
+                           colour >= 2.3 && < 2.4,
+                           transformers == 0.2.*
 
         Exposed-Modules:   Data.GraphViz
                            Data.GraphViz.Types
@@ -80,6 +81,7 @@
         Other-Modules:     Data.GraphViz.Attributes.Internal
                            Data.GraphViz.Types.Clustering
                            Data.GraphViz.Types.Common
+                           Data.GraphViz.Types.State
                            Data.GraphViz.Util
         if flag(test)
            Build-Depends:       QuickCheck >= 2.1 && < 2.2
diff --git a/utils/AttributeGenerator.hs b/utils/AttributeGenerator.hs
new file mode 100644
--- /dev/null
+++ b/utils/AttributeGenerator.hs
@@ -0,0 +1,548 @@
+#!/usr/bin/runhaskell
+
+{- |
+   Module      : AttributeGenerator
+   Description : Definition of the Graphviz attributes.
+   Copyright   : (c) Ivan Lazar Miljenovic
+   License     : 3-Clause BSD-style
+   Maintainer  : Ivan.Miljenovic@gmail.com
+
+   This module is a stand-alone that generates the correct code for
+   Data.GraphViz.Attributes.
+  -}
+module Data.GraphViz.AttributeGenerator where
+
+import Text.PrettyPrint
+import Data.List(transpose)
+import Data.Maybe(catMaybes, isJust, fromJust)
+import Control.Monad(liftM)
+import System.Environment(getArgs)
+
+type Code = Doc
+
+-- If any args are passed in, then generate the Arbitrary instance
+-- instead of the definition.
+main :: IO ()
+main = do args <- getArgs
+          let f = if null args
+                  then genCode
+                  else genArbitrary
+          print $ f att
+    where
+      att = AS { tpNm = text "Attribute"
+               , atts = attributes
+               }
+
+genCode     :: Atts -> Doc
+genCode att = vsep $ map ($att) cds
+    where
+      cds = [ createDefn
+            , createAlias
+            , showInstance
+            , parseInstance
+            , usedByFunc "Graphs" forGraphs
+            , usedByFunc "Clusters" forClusters
+            , usedByFunc "SubGraphs" forSubGraphs
+            , usedByFunc "Nodes" forNodes
+            , usedByFunc "Edges" forEdges
+            , sameAttributeFunc
+            ]
+
+genArbitrary :: Atts -> Doc
+genArbitrary = arbitraryInstance
+
+-- -----------------------------------------------------------------------------
+
+-- Defining data structures
+
+data Atts = AS { tpNm :: Code
+               , atts :: [Attribute]
+               }
+
+data Attribute = A { cnst         :: Code
+                   , name         :: Code
+                   , parseNames   :: [Code]
+                   , valtype      :: VType
+                   , parseDef     :: Maybe Code
+                   , forGraphs    :: Bool
+                   , forClusters  :: Bool
+                   , forSubGraphs :: Bool
+                   , forNodes     :: Bool
+                   , forEdges     :: Bool
+                   , comment      :: Doc
+                   }
+
+makeAttr                   :: Constructor -> [Name] -> UsedBy -> VType
+                              -> Maybe Default -- Used when parsing the field
+                              -> Maybe Default -> Maybe Minimum -> Maybe Comment
+                              -> Attribute
+makeAttr c ns u v df d m cm = A { cnst         = text c
+                                , name         = head ns'
+                                , parseNames   = ns'
+                                , valtype      = v -- just in case need to do fancy
+                                                   -- stuff
+                                , parseDef     = liftM text df
+                                , forGraphs    = isFor 'G'
+                                , forClusters  = isFor 'C' || forSG
+                                , forSubGraphs = forSG
+                                , forNodes     = isFor 'N'
+                                , forEdges     = isFor 'E'
+                                , comment      = cm'
+                                }
+    where
+      ns' = map text ns
+      isFor f = f `elem` u
+      forSG = isFor 'S'
+      df' = if v == Bl then Just "'True'" else fmap ( \ t -> '\'' : t ++ "'") df
+      mDoc (f,fc) = f <> colon <+> text fc
+      addF f = fmap (\ dc -> (wrap (char '/') (text f), dc))
+      cm' = hsep
+            . punctuate semi
+            . map mDoc
+            $ catMaybes [ addF "Valid for" (Just u)
+                        , addF "Default" d
+                        , addF "Parsing Default" df'
+                        , addF "Minimum" m
+                        , addF "Notes" cm
+                        ]
+
+type Constructor = String
+type Name = String
+type UsedBy = String -- should only contain subset of "ENGCS"
+type Default = String
+type Minimum = String
+type Comment = String
+
+data VType = Dbl
+           | Integ
+           | Strng
+           | EStrng
+           | Bl
+           | Cust String
+             deriving (Eq, Ord, Show, Read)
+
+vtype           :: VType -> Doc
+vtype Dbl       = text "Double"
+vtype Integ     = text "Int"
+vtype Strng     = text "String"
+vtype EStrng    = text "EscString"
+vtype Bl        = text "Bool"
+vtype (Cust t)  = text t
+
+vtypeCode :: Attribute -> Code
+vtypeCode = vtype . valtype
+
+-- -----------------------------------------------------------------------------
+
+createDefn     :: Atts -> Code
+createDefn att = hdr $+$ constructors $+$ derivs
+    where
+      hdr = text "data" <+> tpNm att
+      constructors = nest tab
+                     . asRows
+                     . firstOthers equals (char '|')
+                     . (++ [defUnknown])
+                     . map createDefn
+                     $ atts att
+      derivs = nest (tab + 2) $ text "deriving (Eq, Ord, Show, Read)"
+      createDefn a = [cnst a <+> vtypeCode a
+                     , if isEmpty cm
+                       then empty
+                       else text "-- ^" <+> cm
+                     ]
+          where
+            cm = comment a
+      defUnknown = [ unknownAttr <+> vtype Strng <+> vtype Strng
+                   , text "-- ^ /Valid for/: Assumed valid for all; the fields are 'Attribute' name and value respectively."
+                   ]
+
+createAlias     :: Atts -> Code
+createAlias att = text "type"
+                  <+> tp <> char 's'
+                  <+> equals
+                  <+> brackets tp
+    where
+      tp = tpNm att
+
+showInstance     :: Atts -> Code
+showInstance att = hdr $+$ insts'
+    where
+      hdr = text "instance" <+> text "PrintDot" <+> tpNm att <+> text "where"
+      var = char 'v'
+      sFunc = text "unqtDot"
+      cnct = text "<>"
+      insts = asRows
+              . (++ [unknownInst])
+              . map mkInstance
+              $ atts att
+      mkInstance a = [ sFunc <+> parens (cnst a <+> var)
+                     , equals <+> text "printField" <+> doubleQuotes (name a)
+                                  <+>  var
+                     ]
+      unknownInst = [ sFunc <+> parens (unknownAttr <+> char 'a' <+> var)
+                    , equals <+> text "toDot" <+> char 'a'
+                      <+> text "<> equals <>" <+> text "toDot" <+> var
+                    ]
+      insts' = nest tab
+              $ vsep [ insts
+                     , text "listToDot" <+> equals <+> text "unqtListToDot"
+                     ]
+
+parseInstance     :: Atts -> Code
+parseInstance att = hdr $+$ nest tab fns
+    where
+      hdr = text "instance" <+> text "ParseDot" <+> tpNm att <+> text "where"
+      fn = pFunc <+> equals <+> text "oneOf" <+> ops
+      fns = vsep [ fn
+                 , text "parse" <+> equals <+> pFunc
+                 , text "parseList" <+> equals <+> text "parseUnqtList"
+                 ]
+      ops = flip ($$) rbrack
+            . asRows
+            . firstOthers lbrack comma
+            . map return
+            . (++ [pUnknown])
+            . map parseAttr
+            $ atts att
+      pFunc = text "parseUnqt"
+      pType b a
+          | valtype a == Bl     = pFld <> text "Bool" <+> cnst a
+          | isJust $ parseDef a = pFld <> text "Def"  <+> cnst a <+> fromJust (parseDef a)
+          | otherwise           = pFld <+> cnst a
+          where
+            pFld = text "parseField" <> if b then char 's' else empty
+
+      parseAttr a = case map doubleQuotes $ parseNames a of
+                      [n] -> pType False a <+> n
+                      ns  -> pType True  a <+> docList ns
+      pUnknown = text "liftM2" <+> unknownAttr <+> text "stringBlock"
+                 <+> parens (text "parseEq >> parse")
+
+arbitraryInstance     :: Atts -> Code
+arbitraryInstance att = vsep [hdr $+$ fns, kFunc]
+    where
+      hdr = text "instance" <+> text "Arbitrary" <+> tpNm att <+> text "where"
+      fns = nest tab $ vsep [aFn, sFn]
+      aFn = aFunc <+> equals <+> text "oneof" <+> ops
+      ops = flip ($$) rbrack
+            . asRows
+            . firstOthers lbrack comma
+            . (++ [[aUnknown]])
+            . map (return . arbAttr)
+            $ atts att
+      aFunc = text "arbitrary"
+      arbAttr a = text "liftM" <+> cnst a <+> arbitraryFor' a
+      sFn = asRows
+            . (++ [sUnknown])
+            . map shrinkAttr
+            $ atts att
+      sFunc = text "shrink"
+      var = char 'v'
+      shrinkAttr a = [ sFunc <+> parens (cnst a <+> var)
+                     , equals <+> text "map" <+> cnst a
+                     , dollar <+> shrinkFor (valtype a) <+> var
+                     ]
+      aUnknown = text "liftM2" <+> unknownAttr
+                 <+> parens (text "suchThat" <+> text "arbIDString" <+> kFuncNm)
+                 <+> arbitraryFor Strng
+      sUnknown = [ sFunc <+> parens (unknownAttr <+> char 'a' <+> var)
+                 , equals <+> text "liftM2" <+> unknownAttr
+                 , parens (text "liftM" <+> parens (text "filter" <+> kFuncNm)
+                           <+> shrinkFor Strng <+> char 'a')
+                   <+> parens (shrinkFor Strng <+> var)
+                 ]
+
+      kFunc = asRows (kTpSig : kTrs ++ [kOth])
+      kFuncNm = text "validUnknown"
+      kTpSig = [ kFuncNm
+               , colon <> colon <+> text "String -> Bool"
+               ]
+      kTrs = map kTr . concatMap parseNames $ atts att
+      kTr pn = [ kFuncNm <+> doubleQuotes pn
+               , equals <+> text "False"
+               ]
+      kOth = [ kFuncNm <+> char '_'
+             , equals <+> text "True"
+             ]
+
+arbitraryFor                :: VType -> Doc
+arbitraryFor Strng          = text "arbString"
+arbitraryFor EStrng         = text "arbString"
+arbitraryFor (Cust ('[':_)) = text "arbList"
+arbitraryFor _              = text "arbitrary"
+
+arbitraryFor' :: Attribute -> Doc
+arbitraryFor' = arbitraryFor . valtype
+
+shrinkFor :: VType -> Doc
+shrinkFor (Cust ('[':_)) = text "nonEmptyShrinks"
+shrinkFor Strng          = text "shrinkString"
+shrinkFor EStrng         = text "shrinkString"
+shrinkFor _              = text "shrink"
+
+usedByFunc          :: String -> (Attribute -> Bool) -> Atts -> Code
+usedByFunc nm p att = cmnt $$ asRows (tpSig : trs ++ [fls])
+    where
+      nm' = text nm
+      dt = tpNm att
+      cmnt = text "-- | Determine if this '" <> dt
+             <> text "' is valid for use with" <+> nm' <> dot
+      tpSig = [ fn
+              , colon <> colon
+                <+> dt <+> text "->" <+> text "Bool"
+              ]
+      fn = text "usedBy" <> nm'
+      tr = text "True"
+      trs = map aTr as' ++ [unknownATr]
+      fl = text "False"
+      fls = [ fn <+> char '_'
+            , equals <+> fl
+            ]
+      as' = filter p $ atts att
+      aTr a = [ fn <+> cnst a <> braces empty
+              , equals <+> tr
+              ]
+      unknownATr = [ fn <+> unknownAttr <> braces empty
+                   , equals <+> tr
+                   ]
+
+sameAttributeFunc     :: Atts -> Code
+sameAttributeFunc att = cmnt $$ asRows (tpSig : stmts ++ [unknownAtr, rst])
+  where
+    cmnt = text "-- | Determine if two '" <> dt
+           <> text "s' are the same type of '"<> dt <> text"'."
+    sFunc = text "sameAttribute"
+    dt = tpNm att
+    tpSig = [ sFunc
+            , char ' ' -- first arg, for some reason won't line up
+                       -- properly if its empty
+            , empty -- second arg
+            , colon <> colon
+              <+> dt <+> text "->" <+> dt <+> text "->" <+> text "Bool"
+            ]
+    stmts = map sf $ atts att
+    sf a = [ sFunc
+           , cnst a <> braces empty
+           , cnst a <> braces empty
+           , equals <+> tr
+           ]
+    tr = text "True"
+    catchAll = char '_'
+    unknownAtr = [ sFunc
+                 , parens $ unknownAttr <+> text "a1" <+> catchAll
+                 , parens $ unknownAttr <+> text "a2" <+> catchAll
+                 , equals <+> text "a1" <+> equals <> equals <+> text "a2"
+                 ]
+    rst = [ sFunc
+          , catchAll
+          , catchAll
+          , equals <+> text "False"
+          ]
+
+
+-- -----------------------------------------------------------------------------
+
+-- Helper functions
+
+-- Size of a tab character
+tab :: Int
+tab = 4
+
+firstOthers            :: Doc -> Doc -> [[Doc]] -> [[Doc]]
+firstOthers _ _ []     = []
+firstOthers f o (d:ds) = (f : d) : map ((:) o) ds
+
+wrap     :: Doc -> Doc -> Doc
+wrap w d = w <> d <> w
+
+vsep :: [Doc] -> Doc
+vsep = vcat . punctuate newline
+    where
+      newline = char '\n'
+
+asRows    :: [[Doc]] -> Doc
+asRows as = vcat $ map padR asL
+    where
+      asL = map (map (\d -> (d, docLen d))) as
+      cWidths = map (maximum . map snd) $ transpose asL
+      shiftLen rls = let (rs,ls) = unzip rls
+                     in zip rs (0:ls)
+      padR = hsep . zipWith append (0 : cWidths) . shiftLen
+      append l' (d,l) = hcat (repl (l' - l) space) <> d
+      repl n xs | n <= 0    = []
+                | otherwise = replicate n xs
+
+-- A really hacky thing to do, but oh well...
+-- Don't use this for multi-line Docs!
+docLen :: Doc -> Int
+docLen = length . render
+
+docList :: [Doc] -> Doc
+docList = brackets . hsep . punctuate comma
+
+dot :: Doc
+dot = char '.'
+
+-- -----------------------------------------------------------------------------
+
+-- The actual attributes
+
+attributes :: [Attribute]
+attributes = [ makeAttr "Damping" ["Damping"] "G" Dbl Nothing (Just "@0.99@") (Just "@0.0@") (Just "neato only")
+             , makeAttr "K" ["K"] "GC" Dbl Nothing (Just "@0.3@") (Just "@0@") (Just "sfdp, fdp only")
+             , makeAttr "URL" ["URL", "href"] "ENGC" EStrng Nothing (Just "none") Nothing (Just "svg, postscript, map only")
+             , makeAttr "ArrowHead" ["arrowhead"] "E" (Cust "ArrowType") Nothing (Just "@'normal'@") Nothing Nothing
+             , makeAttr "ArrowSize" ["arrowsize"] "E" Dbl Nothing (Just "@1.0@") (Just "@0.0@") Nothing
+             , makeAttr "ArrowTail" ["arrowtail"] "E" (Cust "ArrowType") Nothing (Just "@'normal'@") Nothing Nothing
+             , makeAttr "Aspect" ["aspect"] "G" (Cust "AspectType") Nothing Nothing Nothing (Just "dot only")
+             , makeAttr "Bb" ["bb"] "G" (Cust "Rect") Nothing Nothing Nothing (Just "write only")
+             , makeAttr "BgColor" ["bgcolor"] "GC" (Cust "Color") Nothing (Just "X11Color 'Transparent'") Nothing Nothing
+             , makeAttr "Center" ["center"] "G" Bl (Just "True") (Just "@'False'@") Nothing Nothing
+             , makeAttr "Charset" ["charset"] "G" Strng Nothing (Just "@\\\"UTF-8\\\"@") Nothing Nothing
+             , makeAttr "ClusterRank" ["clusterrank"] "G" (Cust "ClusterMode") Nothing (Just "@'Local'@") Nothing (Just "dot only")
+             , makeAttr "ColorScheme" ["colorscheme"] "ENCG" (Cust "ColorScheme") Nothing (Just "@'X11'@") Nothing Nothing
+             , makeAttr "Color" ["color"] "ENC" (Cust "[Color]") Nothing (Just "@X11Color 'Black'@") Nothing Nothing
+             , makeAttr "Comment" ["comment"] "ENG" Strng Nothing (Just "@\\\"\\\"@") Nothing Nothing
+             , makeAttr "Compound" ["compound"] "G" Bl (Just "True") (Just "@'False'@") Nothing (Just "dot only")
+             , makeAttr "Concentrate" ["concentrate"] "G" Bl (Just "True") (Just "@'False'@") Nothing Nothing
+             , makeAttr "Constraint" ["constraint"] "E" Bl (Just "True")  (Just "@'True'@") Nothing (Just "dot only")
+             , makeAttr "Decorate" ["decorate"] "E" Bl (Just "True") (Just "@'False'@") Nothing Nothing
+             , makeAttr "DefaultDist" ["defaultdist"] "G" Dbl Nothing (Just "@1+(avg. len)*sqrt(|V|)@") (Just "@epsilon@") (Just "neato only")
+             , makeAttr "Dimen" ["dimen"] "G" Integ Nothing (Just "@2@") (Just "@2@") (Just "sfdp, fdp, neato only")
+             , makeAttr "Dim" ["dim"] "G" Integ Nothing (Just "@2@") (Just "@2@") (Just "sfdp, fdp, neato only")
+             , makeAttr "Dir" ["dir"] "E" (Cust "DirType") Nothing (Just "@'Forward'@ (directed), @'NoDir'@ (undirected)") Nothing Nothing
+             , makeAttr "DirEdgeConstraints" ["diredgeconstraints"] "G" (Cust "DEConstraints") (Just "EdgeConstraints") (Just "@'NoConstraints'@") Nothing (Just "neato only")
+             , makeAttr "Distortion" ["distortion"] "N" Dbl Nothing (Just "@0.0@") (Just "@-100.0@") Nothing
+             , makeAttr "DPI" ["dpi", "resolution"] "G" Dbl Nothing (Just "@96.0@, @0.0@") Nothing (Just "svg, bitmap output only; \\\"resolution\\\" is a synonym")
+             , makeAttr "EdgeURL" ["edgeURL", "edgehref"] "E" EStrng Nothing (Just "@\\\"\\\"@") Nothing (Just "svg, map only")
+             , makeAttr "EdgeTarget" ["edgetarget"] "E" EStrng Nothing (Just "none") Nothing (Just "svg, map only")
+             , makeAttr "EdgeTooltip" ["edgetooltip"] "E" EStrng Nothing (Just "@\\\"\\\"@") Nothing (Just "svg, cmap only")
+             , makeAttr "Epsilon" ["epsilon"] "G" Dbl Nothing (Just "@.0001 * # nodes@ (@mode == 'KK'@), @.0001@ (@mode == 'Major'@)") Nothing (Just "neato only")
+             , makeAttr "ESep" ["esep"] "G" (Cust "DPoint") Nothing (Just "@+3@") Nothing (Just "not dot")
+             , makeAttr "FillColor" ["fillcolor"] "NC" (Cust "Color") Nothing (Just "@X11Color 'LightGray'@ (nodes), @X11Color 'Black'@ (clusters)") Nothing Nothing
+             , makeAttr "FixedSize" ["fixedsize"] "N" Bl (Just "True") (Just "@'False'@") Nothing Nothing
+             , makeAttr "FontColor" ["fontcolor"] "ENGC" (Cust "Color") Nothing (Just "@X11Color 'Black'@") Nothing Nothing
+             , makeAttr "FontName" ["fontname"] "ENGC" Strng Nothing (Just "@\\\"Times-Roman\\\"@") Nothing Nothing
+             , makeAttr "FontNames" ["fontnames"] "G" Strng Nothing (Just "@\\\"\\\"@") Nothing (Just "svg only")
+             , makeAttr "FontPath" ["fontpath"] "G" Strng Nothing (Just "system-dependent") Nothing Nothing
+             , makeAttr "FontSize" ["fontsize"] "ENGC" Dbl Nothing (Just "@14.0@") (Just "@1.0@") Nothing
+             , makeAttr "Group" ["group"] "N" Strng Nothing (Just "@\\\"\\\"@") Nothing (Just "dot only")
+             , makeAttr "HeadURL" ["headURL", "headhref"] "E" EStrng Nothing (Just "@\\\"\\\"@") Nothing (Just "svg, map only")
+             , makeAttr "HeadClip" ["headclip"] "E" Bl (Just "True") (Just "@'True'@") Nothing Nothing
+             , makeAttr "HeadLabel" ["headlabel"] "E" (Cust "Label") Nothing (Just "@\\\"\\\"@") Nothing Nothing
+             , makeAttr "HeadPort" ["headport"] "E" (Cust "PortPos") Nothing (Just "@'PP' 'CenterPoint'@") Nothing Nothing
+             , makeAttr "HeadTarget" ["headtarget"] "E" EStrng Nothing (Just "none") Nothing (Just "svg, map only")
+             , makeAttr "HeadTooltip" ["headtooltip"] "E" EStrng Nothing (Just "@\\\"\\\"@") Nothing (Just "svg, cmap only")
+             , makeAttr "Height" ["height"] "N" Dbl Nothing (Just "@0.5@") (Just "@0.02@") Nothing
+             , makeAttr "ID" ["id"] "GNE" (Cust "Label") Nothing (Just "@\\\"\\\"@") Nothing (Just "svg, postscript, map only")
+             , makeAttr "Image" ["image"] "N" Strng Nothing (Just "@\\\"\\\"@") Nothing Nothing
+             , makeAttr "ImageScale" ["imagescale"] "N" (Cust "ScaleType") (Just "UniformScale") (Just "@'NoScale'@") Nothing Nothing
+             , makeAttr "LabelURL" ["labelURL", "labelhref"] "E" EStrng Nothing (Just "@\\\"\\\"@") Nothing (Just "svg, map only")
+             , makeAttr "LabelAngle" ["labelangle"] "E" Dbl Nothing (Just "@-25.0@") (Just "@-180.0@") Nothing
+             , makeAttr "LabelDistance" ["labeldistance"] "E" Dbl Nothing (Just "@1.0@") (Just "@0.0@") Nothing
+             , makeAttr "LabelFloat" ["labelfloat"] "E" Bl (Just "True") (Just "@'False'@") Nothing Nothing
+             , makeAttr "LabelFontColor" ["labelfontcolor"] "E" (Cust "Color") Nothing (Just "@X11Color 'Black'@") Nothing Nothing
+             , makeAttr "LabelFontName" ["labelfontname"] "E" Strng Nothing (Just "@\\\"Times-Roman\\\"@") Nothing Nothing
+             , makeAttr "LabelFontSize" ["labelfontsize"] "E" Dbl Nothing (Just "@14.0@") (Just "@1.0@") Nothing
+             , makeAttr "LabelJust" ["labeljust"] "GC" (Cust "Justification") Nothing (Just "@'JCenter'@") Nothing Nothing
+             , makeAttr "LabelLoc" ["labelloc"] "GCN" (Cust "VerticalPlacement") Nothing (Just "@'VTop'@ (clusters), @'VBottom'@ (root graphs), @'VCenter'@ (nodes)") Nothing Nothing
+             , makeAttr "LabelTarget" ["labeltarget"] "E" EStrng Nothing (Just "none") Nothing (Just "svg, map only")
+             , makeAttr "LabelTooltip" ["labeltooltip"] "E" EStrng Nothing (Just "@\\\"\\\"@") Nothing (Just "svg, cmap only")
+             , makeAttr "Label" ["label"] "ENGC" (Cust "Label") Nothing (Just "@'StrLabel' \\\"\\N\\\"@ (nodes), @'StrLabel' \\\"\\\"@ (otherwise)") Nothing Nothing
+             , makeAttr "Landscape" ["landscape"] "G" Bl (Just "True") (Just "@'False'@") Nothing Nothing
+             , makeAttr "LayerSep" ["layersep"] "G" Strng Nothing (Just "@\\\" :\\t\\\"@") Nothing Nothing
+             , makeAttr "Layers" ["layers"] "G" (Cust "LayerList") Nothing (Just "@\\\"\\\"@") Nothing Nothing
+             , makeAttr "Layer" ["layer"] "EN" (Cust "LayerRange") Nothing (Just "@\\\"\\\"@") Nothing Nothing
+             , makeAttr "Layout" ["layout"] "G" Strng Nothing (Just "@\\\"\\\"@") Nothing Nothing
+             , makeAttr "Len" ["len"] "E" Dbl Nothing (Just "@1.0@ (neato), @0.3@ (fdp)") Nothing (Just "fdp, neato only")
+             , makeAttr "LevelsGap" ["levelsgap"] "G" Dbl Nothing (Just "@0.0@") Nothing (Just "neato only")
+             , makeAttr "Levels" ["levels"] "G" Integ Nothing (Just "@MAXINT@") (Just "@0@") (Just "sfdp only")
+             , makeAttr "LHead" ["lhead"] "E" Strng Nothing (Just "@\\\"\\\"@") Nothing (Just "dot only")
+             , makeAttr "LPos" ["lp"] "EGC" (Cust "Point") Nothing Nothing Nothing (Just "write only")
+             , makeAttr "LTail" ["ltail"] "E" Strng Nothing (Just "@\\\"\\\"@") Nothing (Just "dot only")
+             , makeAttr "Margin" ["margin"] "NG" (Cust "DPoint") Nothing (Just "device-dependent") Nothing Nothing
+             , makeAttr "MaxIter" ["maxiter"] "G" Integ Nothing (Just "@100 * # nodes@ (@mode == 'KK'@), @200@ (@mode == 'Major'@), @600@ (fdp)") Nothing (Just "fdp, neato only")
+             , makeAttr "MCLimit" ["mclimit"] "G" Dbl Nothing (Just "@1.0@") Nothing (Just "dot only")
+             , makeAttr "MinDist" ["mindist"] "G" Dbl Nothing (Just "@1.0@") (Just "@0.0@") (Just "circo only")
+             , makeAttr "MinLen" ["minlen"] "E" Integ Nothing (Just "@1@") (Just "@0@") (Just "dot only")
+             , makeAttr "Model" ["model"] "G" (Cust "Model") Nothing (Just "@'ShortPath'@") Nothing (Just "neato only")
+             , makeAttr "Mode" ["mode"] "G" (Cust "ModeType") Nothing (Just "@'Major'@") Nothing (Just "neato only")
+             , makeAttr "Mosek" ["mosek"] "G" Bl (Just "True") (Just "@'False'@") Nothing (Just "neato only; requires the Mosek software")
+             , makeAttr "NodeSep" ["nodesep"] "G" Dbl Nothing (Just "@0.25@") (Just "@0.02@") (Just "dot only")
+             , makeAttr "NoJustify" ["nojustify"] "GCNE" Bl (Just "True") (Just "@'False'@") Nothing Nothing
+             , makeAttr "Normalize" ["normalize"] "G" Bl (Just "True") (Just "@'False'@") Nothing (Just "not dot")
+             , makeAttr "Nslimit1" ["nslimit1"] "G" Dbl Nothing Nothing Nothing (Just "dot only")
+             , makeAttr "Nslimit" ["nslimit"] "G" Dbl Nothing Nothing Nothing (Just "dot only")
+             , makeAttr "Ordering" ["ordering"] "G" Strng Nothing (Just "@\\\"\\\"@") Nothing (Just "dot only")
+             , makeAttr "Orientation" ["orientation"] "N" Dbl Nothing (Just "@0.0@") (Just "@360.0@") Nothing
+             , makeAttr "OutputOrder" ["outputorder"] "G" (Cust "OutputMode") Nothing (Just "@'BreadthFirst'@") Nothing Nothing
+             , makeAttr "OverlapScaling" ["overlap_scaling"] "G" Dbl Nothing (Just "@-4@") (Just "@-1.0e10@") (Just "prism only")
+             , makeAttr "Overlap" ["overlap"] "G" (Cust "Overlap") (Just "KeepOverlaps") (Just "@'KeepOverlaps'@") Nothing (Just "not dot")
+             , makeAttr "PackMode" ["packmode"] "G" (Cust "PackMode") Nothing (Just "@'PackNode'@") Nothing (Just "not dot")
+             , makeAttr "Pack" ["pack"] "G" (Cust "Pack") (Just "DoPack") (Just "@'False'@") Nothing (Just "not dot")
+             , makeAttr "Pad" ["pad"] "G" (Cust "DPoint") Nothing (Just "@'DVal' 0.0555@ (4 points)") Nothing Nothing
+             , makeAttr "PageDir" ["pagedir"] "G" (Cust "PageDir") Nothing (Just "@'BL'@") Nothing Nothing
+             , makeAttr "Page" ["page"] "G" (Cust "Point") Nothing Nothing Nothing Nothing
+             , makeAttr "PenColor" ["pencolor"] "C" (Cust "Color") Nothing (Just "@X11Color 'Black'@") Nothing Nothing
+             , makeAttr "PenWidth" ["penwidth"] "CNE" Dbl Nothing (Just "@1.0@") (Just "@0.0@") Nothing
+             , makeAttr "Peripheries" ["peripheries"] "NC" Integ Nothing (Just "shape default (nodes), @1@ (clusters)") (Just "0") Nothing
+             , makeAttr "Pin" ["pin"] "N" Bl (Just "True") (Just "@'False'@") Nothing (Just "fdp, neato only")
+             , makeAttr "Pos" ["pos"] "EN" (Cust "Pos") Nothing Nothing Nothing Nothing
+             , makeAttr "QuadTree" ["quadtree"] "G" (Cust "QuadType") (Just "NormalQT") (Just "@'NormalQT'@") Nothing (Just "sfdp only")
+             , makeAttr "Quantum" ["quantum"] "G" Dbl Nothing (Just "@0.0@") (Just "@0.0@") Nothing
+             , makeAttr "RankDir" ["rankdir"] "G" (Cust "RankDir") Nothing (Just "@'TB'@") Nothing (Just "dot only")
+             , makeAttr "RankSep" ["ranksep"] "G" (Cust "[Double]") Nothing (Just "@0.5@ (dot), @1.0@ (twopi)") (Just "0.02") (Just "twopi, dot only")
+             , makeAttr "Rank" ["rank"] "S" (Cust "RankType") Nothing Nothing Nothing (Just "dot only")
+             , makeAttr "Ratio" ["ratio"] "G" (Cust "Ratios") Nothing Nothing Nothing Nothing
+             , makeAttr "Rects" ["rects"] "N" (Cust "Rect") Nothing Nothing Nothing (Just "write only")
+             , makeAttr "Regular" ["regular"] "N" Bl (Just "True") (Just "@'False'@") Nothing Nothing
+             , makeAttr "ReMinCross" ["remincross"] "G" Bl (Just "True") (Just "@'False'@") Nothing (Just "dot only")
+             , makeAttr "RepulsiveForce" ["repulsiveforce"] "G" Dbl Nothing (Just "@1.0@") (Just "@0.0@") (Just "sfdp only")
+             , makeAttr "Root" ["root"] "GN" (Cust "Root") (Just "IsCentral") (Just "@'NodeName' \\\"\\\"@ (graphs), @'NotCentral'@ (nodes)") Nothing (Just "circo, twopi only")
+             , makeAttr "Rotate" ["rotate"] "G" Integ Nothing (Just "@0@") Nothing Nothing
+             , makeAttr "SameHead" ["samehead"] "E" Strng Nothing (Just "@\\\"\\\"@") Nothing (Just "dot only")
+             , makeAttr "SameTail" ["sametail"] "E" Strng Nothing (Just "@\\\"\\\"@") Nothing (Just "dot only")
+             , makeAttr "SamplePoints" ["samplepoints"] "N" Integ Nothing (Just "@8@ (output), @20@ (overlap and image maps)") Nothing Nothing
+             , makeAttr "SearchSize" ["searchsize"] "G" Integ Nothing (Just "@30@") Nothing (Just "dot only")
+             , makeAttr "Sep" ["sep"] "G" (Cust "DPoint") Nothing (Just "@+4@") Nothing (Just "not dot")
+             , makeAttr "ShapeFile" ["shapefile"] "N" Strng Nothing (Just "@\\\"\\\"@") Nothing Nothing
+             , makeAttr "Shape" ["shape"] "N" (Cust "Shape") Nothing (Just "@'Ellipse'@") Nothing Nothing
+             , makeAttr "ShowBoxes" ["showboxes"] "ENG" Integ Nothing (Just "@0@") (Just "@0@") (Just "dot only")
+             , makeAttr "Sides" ["sides"] "N" Integ Nothing (Just "@4@") (Just "@0@") Nothing
+             , makeAttr "Size" ["size"] "G" (Cust "Point") Nothing Nothing Nothing Nothing
+             , makeAttr "Skew" ["skew"] "N" Dbl Nothing (Just "@0.0@") (Just "@-100.0@") Nothing
+             , makeAttr "Smoothing" ["smoothing"] "G" (Cust "SmoothType") Nothing (Just "@'NoSmooth'@") Nothing (Just "sfdp only")
+             , makeAttr "SortV" ["sortv"] "GCN" (Cust "Word16") Nothing (Just "@0@") (Just "@0@") Nothing
+             , makeAttr "Splines" ["splines"] "G" (Cust "EdgeType") (Just "SplineEdges") Nothing Nothing Nothing
+             , makeAttr "Start" ["start"] "G" (Cust "StartType") Nothing (Just "@\\\"\\\"@") Nothing (Just "fdp, neato only")
+             , makeAttr "StyleSheet" ["stylesheet"] "G" Strng Nothing (Just "@\\\"\\\"@") Nothing (Just "svg only")
+             , makeAttr "Style" ["style"] "ENC" (Cust "[StyleItem]") Nothing Nothing Nothing Nothing
+             , makeAttr "TailURL" ["tailURL", "tailhref"] "E" EStrng Nothing (Just "@\\\"\\\"@") Nothing (Just "svg, map only")
+             , makeAttr "TailClip" ["tailclip"] "E" Bl (Just "True") (Just "@'True'@") Nothing Nothing
+             , makeAttr "TailLabel" ["taillabel"] "E" (Cust "Label") Nothing (Just "@\\\"\\\"@") Nothing Nothing
+             , makeAttr "TailPort" ["tailport"] "E" (Cust "PortPos") Nothing (Just "center") Nothing Nothing
+             , makeAttr "TailTarget" ["tailtarget"] "E" EStrng Nothing (Just "none") Nothing (Just "svg, map only")
+             , makeAttr "TailTooltip" ["tailtooltip"] "E" EStrng Nothing (Just "@\\\"\\\"@") Nothing (Just "svg, cmap only")
+             , makeAttr "Target" ["target"] "ENGC" EStrng Nothing (Just "none") Nothing (Just "svg, map only")
+             , makeAttr "Tooltip" ["tooltip"] "NEC" EStrng Nothing (Just "@\\\"\\\"@") Nothing (Just "svg, cmap only")
+             , makeAttr "TrueColor" ["truecolor"] "G" Bl (Just "True") Nothing Nothing (Just "bitmap output only")
+             , makeAttr "Vertices" ["vertices"] "N" (Cust "[Point]") Nothing Nothing Nothing (Just "write only")
+             , makeAttr "ViewPort" ["viewport"] "G" (Cust "ViewPort") Nothing (Just "none") Nothing Nothing
+             , makeAttr "VoroMargin" ["voro_margin"] "G" Dbl Nothing (Just "@0.05@") (Just "@0.0@") (Just "not dot")
+             , makeAttr "Weight" ["weight"] "E" Dbl Nothing (Just "@1.0@") (Just "@0@ (dot), @1@ (neato,fdp,sfdp)") Nothing
+             , makeAttr "Width" ["width"] "N" Dbl Nothing (Just "@0.75@") (Just "@0.01@") Nothing
+             , makeAttr "Z" ["z"] "N" Dbl Nothing (Just "@0.0@") (Just "@-MAXFLOAT@, @-1000@") Nothing
+             ]
+
+unknownAttr :: Doc
+unknownAttr = text "UnknownAttribute"
+
+attrs = take 10 $ drop 5 attributes
+
+attrs' = AS (text "Attribute") attrs
+
+bool       :: a -> a -> Bool -> a
+bool f t b = if b then t else f
+
+dollar :: Doc
+dollar = char '$'
diff --git a/utils/TestParsing.hs b/utils/TestParsing.hs
new file mode 100644
--- /dev/null
+++ b/utils/TestParsing.hs
@@ -0,0 +1,94 @@
+#!/usr/bin/runhaskell
+
+{- |
+   Module      : TestParsing
+   Description : Check if the graphviz parser can parse "real world" Dot code.
+   Copyright   : (c) Ivan Lazar Miljenovic
+   License     : 3-Clause BSD-style
+   Maintainer  : Ivan.Miljenovic@gmail.com
+
+   This module defines a program that determines if the provided files
+   containing Dot code can be properly parsed using graphviz's parsers
+   (with the assumption that the provided code is valid).
+
+-}
+module Main where
+
+import Data.GraphViz
+import Data.GraphViz.Types.Generalised
+import Data.GraphViz.Parsing(runParser, parse, discard, allWhitespace', eof)
+import Data.GraphViz.PreProcessing(preProcess)
+
+import Control.Exception(try, ErrorCall(..), IOException)
+import Control.Monad(liftM)
+import System.Environment(getArgs)
+import System.IO(openFile, IOMode(ReadMode))
+
+-- -----------------------------------------------------------------------------
+
+main :: IO ()
+main = tryParsing =<< getArgs
+  where
+    tryParsing [] = putStrLn "Test that the graphviz library can parse\
+                             \ \"real life\" Dot code by passing a list\n\
+                             \of files in which contain Dot graphs.\n\
+                             \\n\
+                             \One way of using this file:\n\t\
+                             \$ locate -r \".*\\.\\(gv\\|dot\\)$\" -0\
+                             \ | xargs -0 runhaskell TestParsing.hs"
+    tryParsing fs = mapM_ tryParseFile fs
+
+-- -----------------------------------------------------------------------------
+
+
+withParse :: (DotRepr dg n) => (a -> IO String) -> (dg n -> IO ())
+             -> (ErrMsg -> String) -> a -> IO ()
+withParse toStr withDG cmbErr a = do dc <- toStr a
+                                     edg <- tryParse dc
+                                     case edg of
+                                       (Right dg) -> withDG dg
+                                       (Left err) -> do putStrLn "Parsing problem!"
+                                                        putStrLn $ cmbErr err
+                                                        putStrLn  ""
+
+type DG = DotGraph String
+type GDG = GDotGraph String
+type ErrMsg = String
+
+tryParseFile    :: FilePath -> IO ()
+tryParseFile fp = readFile' fp >>= maybeParse
+  where
+    maybeParse (Left err)  = putStrLn $ "Error parsing \"" ++ fp ++ "\":\n\t"
+                                        ++ err ++ "\n"
+    maybeParse (Right dot) = withParse (const $ return dot)
+                                       (tryParseCanon fp)
+                                       (\ e -> fp ++ ": Cannot parse as a GDotGraph:\n"
+                                               ++ e)
+                                       fp
+
+
+tryParseCanon    :: FilePath -> GDG -> IO ()
+tryParseCanon fp = withParse prettyPrint
+                             (const (return ()) . asDG)
+                             (\ e -> fp ++ ": Canonical Form not a DotGraph:\n"
+                                     ++ e)
+  where
+    asDG = flip asTypeOf emptDG
+    emptDG = DotGraph False False Nothing $ DotStmts [] [] [] [] :: DG
+
+tryParse    :: (DotRepr dg n) => String -> IO (Either ErrMsg (dg n))
+tryParse dc = liftM getErrMsg . try
+              $ let (dg, rst) = runParser parse $ preProcess dc
+                in length rst `seq` return dg
+
+getErrMsg :: Either ErrorCall a -> Either ErrMsg a
+getErrMsg = either getEC Right
+  where
+    getEC (ErrorCall e) = Left e
+
+readFile'    :: FilePath -> IO (Either ErrMsg String)
+readFile' fp = liftM getMsg . try
+               $ openFile fp ReadMode >>= hGetContents'
+  where
+    getMsg :: Either IOException String -> Either ErrMsg String
+    getMsg = either (Left . show) Right
