diff --git a/README b/README
--- a/README
+++ b/README
@@ -1,9 +1,50 @@
-This is an update to Swish [1,2] so that it builds with a recent
-Haskell platform.
 
-For more information on this release see [3].
+# Introduction
 
- [1] http://www.ninebynine.org/Software/Swish-0.2.0.html
- [2] http://www.ninebynine.org/RDFNotes/Swish/Intro.html
- [2] https://bitbucket.org/doug_burke/swish/wiki/Home
+Swish - which stands for Semantic Web Inference Scripting in Haskell -
+was written by Graham Klyne as a framework, written in the purely
+functional programming language Haskell, for performing deductions in
+RDF data using a variety of techniques. Swish wass conceived as a
+toolkit for experimenting with RDF inference, and for implementing
+stand-alone RDF file processors (usable in similar style to CWM, but
+with a view to being extensible in declarative style through added
+Haskell function and data value declarations). One of the aims was to
+explore Haskell as "a scripting language for the Semantic Web"
+(http://www.ninebynine.org/RDFNotes/Swish/Intro.html).
 
+It was updated from version 0.2.1 by Vasili I Galchin so that it would
+build with recent version of GHC
+(http://hackage.haskell.org/package/swish-0.2.1).
+
+Since then it has been updated to take advantage of recent
+developments in the Haskell ecosystem, add support for the NTriples
+and Turtle serialisation formats, and a number of convenience
+functions. Development is done at
+https://bitbucket.org/doug_burke/swish/.
+
+# Aim
+
+Current development is based on my own needs, which are more about
+using this as a RDF library for I/O with limited querying rather than
+for inferencing or use as a flexible graph-processing library
+(e.g. for extensions to non-RDF models).
+
+# Haskell and the Semantic Web 
+
+Other Haskell packages for RDF support include
+
+ * rdf4h http://hackage.haskell.org/package/rdf4h
+ * hsparql http://hackage.haskell.org/package/hsparql
+ * hasparql-client http://hackage.haskell.org/package/hasparql-client
+
+# Installation
+
+Install a recent version of the Haskell platform (http://hackage.haskell.org/platform/)
+and then try
+
+    % cabal update
+    % cabal install swish
+
+This will install a command-line tool `Swish` along with the modules
+in the `Swish` namespace; documentation can be found at
+http://hackage.haskell.org/package/swish .
diff --git a/src/Swish/RDF/Formatter/Internal.hs b/src/Swish/RDF/Formatter/Internal.hs
--- a/src/Swish/RDF/Formatter/Internal.hs
+++ b/src/Swish/RDF/Formatter/Internal.hs
@@ -20,23 +20,60 @@
 
 module Swish.RDF.Formatter.Internal
     ( NodeGenLookupMap
+    , SLens(..)
     , SubjTree
     , PredTree
     , LabelContext(..)
     , NodeGenState(..)
+    , changeState
+    , hasMore
     , emptyNgs 
+    , getBNodeLabel
     , findMaxBnode
+    , splitOnLabel
     , getCollection
     , processArcs
     , findPrefix
+      -- N3-like formatting
+    , quoteB
+    , quoteText
+    , showScopedName
+    , formatScopedName
+    , formatPrefixLines
+    , formatPlainLit
+    , formatLangLit
+    , formatTypedLit
+    , insertList
+    , nextLine_
+    , mapBlankNode_
+    , formatPrefixes_
+    , formatGraph_
+    , formatSubjects_
+    , formatProperties_
+    , formatObjects_
+    , insertBnode_
+    , extractList_
     )
 where
 
 import Swish.GraphClass (Arc(..), ArcSet)
+import Swish.Namespace (ScopedName, getScopeLocal, getScopeURI)
+import Swish.QName (getLName)
+
 import Swish.RDF.Graph (RDFGraph, RDFLabel(..), NamespaceMap)
-import Swish.RDF.Graph (labels, getArcs, resRdfFirst, resRdfRest, resRdfNil)
+import Swish.RDF.Graph (labels, getArcs
+                       , getNamespaces
+                       , resRdfFirst, resRdfRest, resRdfNil
+                       , quote
+                       , quoteT
+                       )
+import Swish.RDF.Vocabulary (LanguageTag, fromLangTag, xsdBoolean, xsdDecimal, xsdInteger, xsdDouble)
 
-import Data.List (delete, foldl', groupBy)
+import Control.Monad (liftM)
+import Control.Monad.State (State, get, gets, modify, put)
+
+import Data.List (delete, foldl', groupBy, intersperse, partition)
+import Data.Monoid (Monoid(..), mconcat)
 import Data.Word
 
 import Network.URI (URI)
@@ -44,6 +81,8 @@
 
 import qualified Data.Map as M
 import qualified Data.Set as S
+import qualified Data.Text as T
+import qualified Data.Text.Lazy.Builder as B
 
 #if defined(__GLASGOW_HASKELL__) && (__GLASGOW_HASKELL__ >= 701)
 import Data.Tuple (swap)
@@ -55,34 +94,97 @@
 findPrefix :: URI -> M.Map a URI -> Maybe a
 findPrefix u = M.lookup u . M.fromList . map swap . M.assocs
 
+{- 
+
+Playing around with ideas to reduce the amount of duplicated code
+without (for instance) deciding on one of the many lens packages
+available. It does not seem worth further re-factoring until we
+have another formatter using a turtle-like syntax (e.g. TriG
+http://www4.wiwiss.fu-berlin.de/bizer/trig/).
+
+-}
+
+data SLens a b = SLens (a -> b) (a -> b -> a)
+
+-- | Extract the setter.
+slens :: SLens a b -> a -> b -> a
+slens (SLens _ s) = s
+
+-- | Extract the getter.
+glens :: SLens a b -> a -> b
+glens (SLens g _) = g
+
 -- | Node name generation state information that carries through
 --  and is updated by nested formulae.
 type NodeGenLookupMap = M.Map RDFLabel Word32
 
+{-
+TODO: look at using Swish.Graphpartition instead.
+-}
 type SubjTree lb = [(lb,PredTree lb)]
 type PredTree lb = [(lb,[lb])]
 
--- simple context for label creation
--- (may be a temporary solution to the problem
---  of label creation)
+-- | The context for label creation.
 --
 data LabelContext = SubjContext | PredContext | ObjContext
                     deriving (Eq, Show)
 
+-- | A generator for BNode labels.
 data NodeGenState = Ngs
-    { prefixes  :: NamespaceMap
-    , nodeMap   :: NodeGenLookupMap
+    { nodeMap   :: NodeGenLookupMap
     , nodeGen   :: Word32
     }
 
+-- | Create an empty node generator.
 emptyNgs :: NodeGenState
-emptyNgs = Ngs
-    { prefixes  = M.empty
-    , nodeMap   = M.empty
-    , nodeGen   = 0
-    }
+emptyNgs = Ngs M.empty 0
 
 {-|
+Get the label text for the blank node, creating a new one
+if it has not been seen before.
+
+The label text is currently _:swish<number> where number is
+1 or higher. This format may be changed in the future.
+-}
+getBNodeLabel :: RDFLabel -> NodeGenState -> (B.Builder, Maybe NodeGenState)
+getBNodeLabel lab ngs = 
+    let cmap = nodeMap ngs
+        cval = nodeGen ngs
+
+        (lnum, mngs) = 
+            case M.findWithDefault 0 lab cmap of
+              0 -> let nval = succ cval
+                       nmap = M.insert lab nval cmap
+                   in (nval, Just (ngs { nodeGen = nval, nodeMap = nmap }))
+
+              n -> (n, Nothing)
+
+    in ("_:swish" `mappend` B.fromString (show lnum), mngs)
+
+
+{-|
+Process the state, returning a value extracted from it
+after updating the state.
+-}
+
+changeState ::
+    (a -> (b, a)) -> State a b
+changeState f = do
+  st <- get
+  let (rval, nst) = f st
+  put nst
+  return rval
+
+{-|
+Apply the function to the state and return True
+if the result is not empty.
+-}
+
+hasMore :: (a -> [b]) -> State a Bool
+hasMore lens = (not . null . lens) `liftM` get
+
+
+{-|
 Removes the first occurrence of the item from the
 association list, returning it's contents and the rest
 of the list, if it exists.
@@ -191,6 +293,16 @@
             _   -> 0
 getAutoBnodeIndex _                   = 0
 
+splitOnLabel :: 
+    (Eq a) => a -> SubjTree a -> (SubjTree a, PredTree a)
+splitOnLabel lbl osubjs = 
+    let (bsubj, rsubjs) = partition ((== lbl) . fst) osubjs
+        rprops = case bsubj of
+                   [(_, rs)] -> rs
+                   _         -> []
+    in (rsubjs, rprops)
+  
+
 {-
 Find all blank nodes that occur
   - any number of times as a subject
@@ -227,6 +339,309 @@
 
       ctr orig (Arc _ p o) = inc (incP orig p) o
 
+-- N3-like output
+
+-- temporary conversion
+quoteB :: Bool -> String -> B.Builder
+quoteB f v = B.fromString $ quote f v
+
+{-|
+Convert text into a format for display in Turtle. The idea
+is to use one double quote unless three are needed, and to
+handle adding necessary @\\@ characters, or conversion
+for Unicode characters.
+-}
+quoteText :: T.Text -> B.Builder
+quoteText txt = 
+  let st = T.unpack txt -- TODO: fix
+      qst = quoteB (n==1) st
+      n = if '\n' `elem` st || '"' `elem` st then 3 else 1
+      qch = B.fromString (replicate n '"')
+  in mconcat [qch, qst, qch]
+
+-- TODO: need to be a bit more clever with this than we did in NTriples
+--       not sure the following counts as clever enough ...
+--  
+showScopedName :: ScopedName -> B.Builder
+showScopedName = quoteB True . show
+
+formatScopedName :: ScopedName -> M.Map (Maybe T.Text) URI -> B.Builder
+formatScopedName sn prmap =
+  let nsuri = getScopeURI sn
+      local = getLName $ getScopeLocal sn
+  in case findPrefix nsuri prmap of
+       Just (Just p) -> B.fromText $ quoteT True $ mconcat [p, ":", local]
+       _             -> mconcat [ "<"
+                                , quoteB True (show nsuri ++ T.unpack local)
+                                , ">"
+                                ]
+
+formatPlainLit :: T.Text -> B.Builder
+formatPlainLit = quoteText
+
+formatLangLit :: T.Text -> LanguageTag -> B.Builder
+formatLangLit lit lcode = mconcat [quoteText lit, "@", B.fromText (fromLangTag lcode)]
+
+-- The canonical notation for xsd:double in XSD, with an upper-case E,
+-- does not match the syntax used in N3, so we need to convert here.     
+-- Rather than converting back to a Double and then displaying that       
+-- we just convert E to e for now.      
+--      
+formatTypedLit :: T.Text -> ScopedName -> B.Builder
+formatTypedLit lit dtype
+    | dtype == xsdDouble = B.fromText $ T.toLower lit
+    | dtype `elem` [xsdBoolean, xsdDecimal, xsdInteger] = B.fromText lit
+    | otherwise = mconcat [quoteText lit, "^^", showScopedName dtype]
+                           
+	       
+{-
+Add a list inline. We are given the labels that constitute
+the list, in order, so just need to display them surrounded
+by ().
+-}
+insertList ::
+    (RDFLabel -> State a B.Builder)
+    -> [RDFLabel]
+    -> State a B.Builder
+insertList _ [] = return "()" -- QUS: can this happen in a valid graph?
+insertList f xs = do
+    ls <- mapM f xs
+    return $ mconcat ("( " : intersperse " " ls) `mappend` " )" 
+
+nextLine_ ::
+    (a -> B.Builder)      -- ^ indentation
+    -> SLens a Bool       -- ^ line break lens
+    -> B.Builder -> State a B.Builder
+nextLine_ indent _lineBreak str = do
+  ind <- gets indent
+  brk <- gets $ glens _lineBreak
+  if brk
+    then return $ ind `mappend` str
+    else do
+      --  After first line, always insert line break
+      modify $ \st -> slens _lineBreak st True
+      return str
+
+mapBlankNode_ :: SLens a NodeGenState -> RDFLabel -> State a B.Builder
+mapBlankNode_ _nodeGen lab = do
+  ngs <- gets $ glens _nodeGen
+  let (lval, mngs) = getBNodeLabel lab ngs
+  case mngs of
+    Just ngs' -> modify $ \st -> slens _nodeGen st ngs'
+    _ -> return ()
+  return lval
+
+formatPrefixLines :: NamespaceMap -> [B.Builder]
+formatPrefixLines = map pref . M.assocs
+    where
+      pref (Just p,u) = mconcat ["@prefix ", B.fromText p, ": <", quoteB True (show u), "> ."]
+      pref (_,u)      = mconcat ["@prefix : <", quoteB True (show u), "> ."]
+
+formatPrefixes_ ::
+    (B.Builder -> State a B.Builder)  -- ^ Create a new line
+    -> NamespaceMap
+    -> State a B.Builder
+formatPrefixes_ nextLine pmap = 
+    mconcat `liftM` mapM nextLine (formatPrefixLines pmap)
+
+formatGraph_ :: 
+    (B.Builder -> State a ()) -- set indent
+    -> (Bool -> State a ())   -- set line-break flag
+    -> (RDFGraph -> a -> a)        -- create a new state from the graph
+    -> (NamespaceMap -> State a B.Builder) -- format prefixes
+    -> (a -> SubjTree RDFLabel)      -- get the subjects
+    -> State a B.Builder              -- format the subjects
+    -> B.Builder     -- indentation string
+    -> B.Builder  -- text to be placed after final statement
+    -> Bool       -- True if a line break is to be inserted at the start
+    -> Bool       -- True if prefix strings are to be generated
+    -> RDFGraph   -- graph to convert
+    -> State a B.Builder
+formatGraph_ setIndent setLineBreak newState formatPrefixes subjs formatSubjects ind end dobreak dopref gr = do
+  setIndent ind
+  setLineBreak dobreak
+  modify (newState gr)
+  
+  fp <- if dopref
+        then formatPrefixes (getNamespaces gr)
+        else return mempty
+  more <- hasMore subjs
+  if more
+    then do
+      fr <- formatSubjects
+      return $ mconcat [fp, fr, end]
+    else return fp
+
+formatSubjects_ ::
+    State a RDFLabel     -- ^ next subject
+    -> (LabelContext -> RDFLabel -> State a B.Builder)  -- ^ convert label into text
+    -> (a -> PredTree RDFLabel) -- ^ extract properties
+    -> (RDFLabel -> B.Builder -> State a B.Builder)   -- ^ format properties
+    -> (a -> SubjTree RDFLabel) -- ^ extract subjects
+    -> (B.Builder -> State a B.Builder) -- ^ next line
+    -> State a B.Builder
+formatSubjects_ nextSubject formatLabel props formatProperties subjs nextLine = do
+  sb    <- nextSubject
+  sbstr <- formatLabel SubjContext sb
+  
+  flagP <- hasMore props
+  if flagP
+    then do
+      prstr <- formatProperties sb sbstr
+      flagS <- hasMore subjs
+      if flagS
+        then do
+          fr <- formatSubjects_ nextSubject formatLabel props formatProperties subjs nextLine
+          return $ mconcat [prstr, " .", fr]
+        else return prstr
+           
+    else do
+      txt <- nextLine sbstr
+    
+      flagS <- hasMore subjs
+      if flagS
+        then do
+          fr <- formatSubjects_ nextSubject formatLabel props formatProperties subjs nextLine
+          return $ mconcat [txt, " .", fr]
+        else return txt
+
+
+{-
+TODO: now we are throwing a Builder around it is awkward to
+get the length of the text to calculate the indentation
+
+So
+
+  a) change the indentation scheme
+  b) pass around text instead of builder
+
+mkIndent :: L.Text -> L.Text
+mkIndent inVal = L.replicate (L.length inVal) " "
+-}
+
+hackIndent :: B.Builder
+hackIndent = "    "
+
+formatProperties_ :: 
+    (RDFLabel -> State a RDFLabel)        -- ^ next property for the given subject
+    -> (LabelContext -> RDFLabel -> State a B.Builder) -- ^ convert label into text
+    -> (RDFLabel -> RDFLabel -> B.Builder -> State a B.Builder) -- ^ format objects
+    -> (a -> PredTree RDFLabel) -- ^ extract properties
+    -> (B.Builder -> State a B.Builder) -- ^ next line
+    -> RDFLabel             -- ^ property being processed
+    -> B.Builder            -- ^ current output
+    -> State a B.Builder
+formatProperties_ nextProperty formatLabel formatObjects props nextLine sb sbstr = do
+  pr <- nextProperty sb
+  prstr <- formatLabel PredContext pr
+  obstr <- formatObjects sb pr $ mconcat [sbstr, " ", prstr]
+  more  <- hasMore props
+  let sbindent = hackIndent -- mkIndent sbstr
+  if more
+    then do
+      fr <- formatProperties_ nextProperty formatLabel formatObjects props nextLine sb sbindent
+      nl <- nextLine $ obstr `mappend` " ;"
+      return $ nl `mappend` fr
+    else nextLine obstr
+
+formatObjects_ :: 
+    (RDFLabel -> RDFLabel -> State a RDFLabel) -- ^ get the next object for the (subject,property) pair
+    -> (LabelContext -> RDFLabel -> State a B.Builder) -- ^ format a label
+    -> (a -> [RDFLabel])   -- ^ extract objects
+    -> (B.Builder -> State a B.Builder) -- ^ insert a new line
+    -> RDFLabel      -- ^ subject
+    -> RDFLabel      -- ^ property
+    -> B.Builder     -- ^ current text
+    -> State a B.Builder
+formatObjects_ nextObject formatLabel objs nextLine sb pr prstr = do
+  ob    <- nextObject sb pr
+  obstr <- formatLabel ObjContext ob
+  more  <- hasMore objs
+  if more
+    then do
+      let prindent = hackIndent -- mkIndent prstr
+      fr <- formatObjects_ nextObject formatLabel objs nextLine sb pr prindent
+      nl <- nextLine $ mconcat [prstr, " ", obstr, ","]
+      return $ nl `mappend` fr
+    else return $ mconcat [prstr, " ", obstr]
+
+{-
+Processing a Bnode when not a subject.
+-}
+insertBnode_ ::
+    (a -> SubjTree RDFLabel)  -- ^ extract subjects
+    -> (a -> PredTree RDFLabel) -- ^ extract properties
+    -> (a -> [RDFLabel]) -- ^ extract objects
+    -> (a -> SubjTree RDFLabel -> PredTree RDFLabel -> [RDFLabel] -> a) -- ^ update state to new settings
+    -> (RDFLabel -> B.Builder -> State a B.Builder) -- ^ format properties
+    -> RDFLabel
+    -> State a B.Builder
+insertBnode_ subjs props objs updateState formatProperties lbl = do
+  ost <- get
+  let osubjs = subjs ost
+      (rsubjs, rprops) = splitOnLabel lbl osubjs
+  put $ updateState ost rsubjs rprops []
+  flag <- hasMore props
+  txt <- if flag
+         then (`mappend` "\n") `liftM` formatProperties lbl ""
+         else return ""
+
+  -- restore the original data (where appropriate)
+  nst <- get
+  let slist  = map fst $ subjs nst
+      nsubjs = filter (\(l,_) -> l `elem` slist) osubjs
+
+  put $ updateState nst nsubjs (props ost) (objs ost)
+
+  -- TODO: handle indentation?
+  return $ mconcat ["[", txt, "]"]
+
+
+maybeExtractList :: 
+  SubjTree RDFLabel
+  -> PredTree RDFLabel
+  -> LabelContext
+  -> RDFLabel
+  -> Maybe ([RDFLabel], SubjTree RDFLabel, PredTree RDFLabel)
+maybeExtractList osubjs oprops lctxt ln =
+  let mlst = getCollection osubjs' ln
+
+      -- we only want to send in rdf:first/rdf:rest here
+      fprops = filter ((`elem` [resRdfFirst, resRdfRest]) . fst) oprops
+
+      osubjs' =
+          case lctxt of
+            SubjContext -> (ln, fprops) : osubjs
+            _ -> osubjs 
+
+  in case mlst of
+    Just (sl, ls, _) -> 
+      let oprops' = if lctxt == SubjContext
+                    then filter ((`notElem` [resRdfFirst, resRdfRest]) . fst) oprops
+                    else oprops
+      in Just (ls, sl, oprops')
+
+    _ -> Nothing
+
+extractList_ :: 
+    (a -> SubjTree RDFLabel) -- ^ extract subjects
+    -> (a -> PredTree RDFLabel) -- ^ extract properties
+    -> (SubjTree RDFLabel -> State a ())  -- ^ set subjects
+    -> (PredTree RDFLabel -> State a ())  -- ^ set properties
+    -> LabelContext 
+    -> RDFLabel 
+    -> State a (Maybe [RDFLabel])
+extractList_ subjs props setSubjs setProps lctxt ln = do
+  osubjs <- gets subjs
+  oprops <- gets props
+  case maybeExtractList osubjs oprops lctxt ln of
+    Just (ls, osubjs', oprops') -> do
+      setSubjs osubjs'
+      setProps oprops'
+      return (Just ls)
+
+    _ -> return Nothing
+  
 --------------------------------------------------------------------------------
 --
 --  Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin,
diff --git a/src/Swish/RDF/Formatter/N3.hs b/src/Swish/RDF/Formatter/N3.hs
--- a/src/Swish/RDF/Formatter/N3.hs
+++ b/src/Swish/RDF/Formatter/N3.hs
@@ -61,15 +61,32 @@
 where
 
 import Swish.RDF.Formatter.Internal (NodeGenLookupMap, SubjTree, PredTree
+                                    , SLens(..)
                                     , LabelContext(..)
-                                    , NodeGenState(..), emptyNgs
+                                    , NodeGenState(..)
+                                    , changeState
+                                    , hasMore
+                                    , emptyNgs
                                     , findMaxBnode
-                                    , getCollection
                                     , processArcs
-				    , findPrefix) 
+				    , quoteB
+				    , formatScopedName
+				    , formatPlainLit
+				    , formatLangLit
+				    , formatTypedLit
+				    , insertList
+                                    , nextLine_
+                                    , mapBlankNode_
+                                    , formatPrefixes_
+                                    , formatGraph_
+                                    , formatSubjects_
+                                    , formatProperties_
+                                    , formatObjects_
+                                    , insertBnode_
+                                    , extractList_
+				    ) 
 
-import Swish.Namespace (ScopedName, getScopeLocal, getScopeURI)
-import Swish.QName (getLName)
+import Swish.Namespace (ScopedName)
 
 import Swish.RDF.Graph (
   RDFGraph, RDFLabel(..),
@@ -79,24 +96,18 @@
   setNamespaces, getNamespaces,
   getFormulae,
   emptyRDFGraph
-  , quote
-  , quoteT
-  , resRdfFirst, resRdfRest
   )
 
 import Swish.RDF.Vocabulary (
-  fromLangTag, 
   rdfType,
   rdfNil,
   owlSameAs, logImplies
-  , xsdBoolean, xsdDecimal, xsdInteger, xsdDouble 
   )
 
-import Control.Monad (liftM, when, void)
-import Control.Monad.State (State, modify, get, put, runState)
+import Control.Monad (liftM, void)
+import Control.Monad.State (State, modify, get, gets, put, runState)
 
 import Data.Char (isDigit)
-import Data.List (partition, intersperse)
 import Data.Monoid (Monoid(..))
 import Data.Word (Word32)
 
@@ -109,10 +120,6 @@
 import qualified Data.Text.Lazy as L
 import qualified Data.Text.Lazy.Builder as B
 
--- temporary conversion
-quoteB :: Bool -> String -> B.Builder
-quoteB f v = B.fromString $ quote f v
-
 ----------------------------------------------------------------------
 --  Graph formatting state monad
 ----------------------------------------------------------------------
@@ -132,15 +139,27 @@
     , objs      :: [RDFLabel]          -- for last property selected
     , formAvail :: FormulaMap RDFLabel
     , formQueue :: [(RDFLabel,RDFGraph)]
+    , prefixes  :: NamespaceMap
     , nodeGenSt :: NodeGenState
     , bNodesCheck   :: [RDFLabel]      -- these bNodes are not to be converted to '[..]' format
     , traceBuf  :: [String]
     }
-             
+
+type SL a = SLens N3FormatterState a
+
+_lineBreak :: SL Bool
+_lineBreak = SLens lineBreak    $ \a b -> a { lineBreak = b }
+
+_nodeGen :: SL NodeGenState
+_nodeGen   = SLens nodeGenSt    $ \a b -> a { nodeGenSt = b }
+
 type Formatter a = State N3FormatterState a
 
-emptyN3FS :: NodeGenState -> N3FormatterState
-emptyN3FS ngs = N3FS
+updateState :: N3FormatterState -> SubjTree RDFLabel -> PredTree RDFLabel -> [RDFLabel] -> N3FormatterState
+updateState ost nsubjs nprops nobjs = ost { subjs = nsubjs, props = nprops, objs = nobjs }
+
+emptyN3FS :: NamespaceMap -> NodeGenState -> N3FormatterState
+emptyN3FS pmap ngs = N3FS
     { indent    = "\n"
     , lineBreak = False
     , graph     = emptyRDFGraph
@@ -149,41 +168,21 @@
     , objs      = []
     , formAvail = emptyFormulaMap
     , formQueue = []
+    , prefixes  = pmap
     , nodeGenSt = ngs
     , bNodesCheck   = []
     , traceBuf  = []
     }
 
-getIndent :: Formatter B.Builder
-getIndent = indent `liftM` get
-
 setIndent :: B.Builder -> Formatter ()
 setIndent ind = modify $ \st -> st { indent = ind }
 
-getLineBreak :: Formatter Bool
-getLineBreak = lineBreak `liftM` get
-
 setLineBreak :: Bool -> Formatter ()
 setLineBreak brk = modify $ \st -> st { lineBreak = brk }
 
-getNgs :: Formatter NodeGenState
-getNgs = nodeGenSt `liftM` get
-
-setNgs :: NodeGenState -> Formatter ()
-setNgs ngs = modify $ \st -> st { nodeGenSt = ngs }
-
-getPrefixes :: Formatter NamespaceMap
-getPrefixes = prefixes `liftM` getNgs
-
-getSubjs :: Formatter (SubjTree RDFLabel)
-getSubjs = subjs `liftM` get
-
 setSubjs :: SubjTree RDFLabel -> Formatter ()
 setSubjs sl = modify $ \st -> st { subjs = sl }
 
-getProps :: Formatter (PredTree RDFLabel)
-getProps = props `liftM` get
-
 setProps :: PredTree RDFLabel -> Formatter ()
 setProps ps = modify $ \st -> st { props = ps }
 
@@ -197,9 +196,6 @@
   put $ st { objs = os }
 -}
 
-getBnodesCheck :: Formatter [RDFLabel]
-getBnodesCheck = bNodesCheck `liftM` get
-
 {-
 addTrace :: String -> Formatter ()
 addTrace tr = do
@@ -253,29 +249,7 @@
 
 -}
 extractList :: LabelContext -> RDFLabel -> Formatter (Maybe [RDFLabel])
-extractList lctxt ln = do
-  osubjs <- getSubjs
-  oprops <- getProps
-  let mlst = getCollection osubjs' ln
-
-      -- we only want to send in rdf:first/rdf:rest here
-      fprops = filter ((`elem` [resRdfFirst, resRdfRest]) . fst) oprops
-
-      osubjs' =
-          case lctxt of
-            SubjContext -> (ln, fprops) : osubjs
-            _ -> osubjs 
-
-      -- tr = "extractList " ++ show ln ++ " (" ++ show lctxt ++ ")\n -> osubjs= " ++ show osubjs ++ "\n -> opreds= " ++ show oprops ++ "\n -> mlst= " ++ show mlst ++ "\n"
-  -- addTrace tr
-  case mlst of
-    -- sl is guaranteed to be free of (ln,fprops) here if lctxt is SubjContext
-    Just (sl,ls,_) -> do
-              setSubjs sl
-              when (lctxt == SubjContext) $ setProps $ filter ((`notElem` [resRdfFirst, resRdfRest]) . fst) oprops
-              return (Just ls)
-
-    Nothing -> return Nothing
+extractList = extractList_ subjs props setSubjs setProps
   
 ----------------------------------------------------------------------
 --  Define a top-level formatter function:
@@ -311,12 +285,9 @@
   -> (B.Builder, NodeGenLookupMap, Word32, [String])
 formatGraphDiag indnt flag gr = 
   let fg  = formatGraph indnt " .\n" False flag gr
-      ngs = emptyNgs {
-        prefixes = M.empty,
-        nodeGen  = findMaxBnode gr
-        }
+      ngs = emptyNgs { nodeGen = findMaxBnode gr }
              
-      (out, fgs) = runState fg (emptyN3FS ngs)
+      (out, fgs) = runState fg (emptyN3FS emptyNamespaceMap ngs)
       ogs        = nodeGenSt fgs
   
   in (out, nodeMap ogs, nodeGen ogs, traceBuf fgs)
@@ -332,136 +303,42 @@
   -> Bool       -- True if prefix strings are to be generated
   -> RDFGraph   -- graph to convert
   -> Formatter B.Builder
-formatGraph ind end dobreak dopref gr = do
-  setIndent ind
-  setLineBreak dobreak
-  setGraph gr
-  
-  fp <- if dopref
-        then formatPrefixes (getNamespaces gr)
-        else return mempty
-  more <- moreSubjects
-  if more
-    then do
-      fr <- formatSubjects
-      return $ mconcat [fp, fr, end]
-    else return fp
+formatGraph = formatGraph_ setIndent setLineBreak newState formatPrefixes subjs formatSubjects
 
 formatPrefixes :: NamespaceMap -> Formatter B.Builder
-formatPrefixes pmap = do
-  let mls = map pref $ M.assocs pmap
-  ls <- sequence mls
-  return $ mconcat ls
-    where
-      pref (Just p,u) = nextLine $ mconcat ["@prefix ", B.fromText p, ": <", quoteB True (show u), "> ."]
-      pref (_,u)      = nextLine $ mconcat ["@prefix : <", quoteB True (show u), "> ."]
-
-{-
-NOTE:
-I expect there to be confusion below where I need to
-convert from Text to Builder
--}
+formatPrefixes = formatPrefixes_ nextLine
 
 formatSubjects :: Formatter B.Builder
-formatSubjects = do
-  sb    <- nextSubject
-  sbstr <- formatLabel SubjContext sb
-  
-  flagP <- moreProperties
-  if flagP
-    then do
-      prstr <- formatProperties sb sbstr
-      flagS <- moreSubjects
-      if flagS
-        then do
-          fr <- formatSubjects
-          return $ mconcat [prstr, " .", fr]
-        else return prstr
-           
-    else do
-      txt <- nextLine sbstr
-    
-      flagS <- moreSubjects
-      if flagS
-        then do
-          fr <- formatSubjects
-          return $ mconcat [txt, " .", fr]
-        else return txt
-
-{-
-TODO: now we are throwing a Builder around it is awkward to
-get the length of the text to calculate the indentation
-
-So
-
-  a) change the indentation scheme
-  b) pass around text instead of builder
-
-mkIndent :: L.Text -> L.Text
-mkIndent inVal = L.replicate (L.length inVal) " "
--}
-
-hackIndent :: B.Builder
-hackIndent = "    "
+formatSubjects = formatSubjects_ nextSubject formatLabel props formatProperties subjs nextLine
 
 formatProperties :: RDFLabel -> B.Builder -> Formatter B.Builder
-formatProperties sb sbstr = do
-  pr <- nextProperty sb
-  prstr <- formatLabel PredContext pr
-  obstr <- formatObjects sb pr $ mconcat [sbstr, " ", prstr]
-  more  <- moreProperties
-  let sbindent = hackIndent -- mkIndent sbstr
-  if more
-    then do
-      fr <- formatProperties sb sbindent
-      nl <- nextLine $ obstr `mappend` " ;"
-      return $ nl `mappend` fr
-    else nextLine obstr
+formatProperties = formatProperties_ nextProperty formatLabel formatObjects props nextLine
 
 formatObjects :: RDFLabel -> RDFLabel -> B.Builder -> Formatter B.Builder
-formatObjects sb pr prstr = do
-  ob    <- nextObject sb pr
-  obstr <- formatLabel ObjContext ob
-  more  <- moreObjects
-  if more
-    then do
-      let prindent = hackIndent -- mkIndent prstr
-      fr <- formatObjects sb pr prindent
-      nl <- nextLine $ mconcat [prstr, " ", obstr, ","]
-      return $ nl `mappend` fr
-    else return $ mconcat [prstr, " ", obstr]
+formatObjects = formatObjects_ nextObject formatLabel objs nextLine
 
 insertFormula :: RDFGraph -> Formatter B.Builder
 insertFormula gr = do
-  ngs0  <- getNgs
-  ind   <- getIndent
+  pmap0 <- gets prefixes
+  ngs0  <- gets nodeGenSt
+  ind   <- gets indent
   let grm = formatGraph (ind `mappend` "    ") "" True False
             (setNamespaces emptyNamespaceMap gr)
 
-      (f3str, fgs') = runState grm (emptyN3FS ngs0)
+      (f3str, fgs') = runState grm (emptyN3FS pmap0 ngs0)
 
-  setNgs (nodeGenSt fgs')
+  modify $ \st -> st { nodeGenSt = nodeGenSt fgs'
+                     , prefixes  = prefixes fgs' }
   f4str <- nextLine " } "
   return $ mconcat [" { ",f3str, f4str]
 
 {-
-Add a list inline. We are given the labels that constitute
-the list, in order, so just need to display them surrounded
-by ().
--}
-insertList :: [RDFLabel] -> Formatter B.Builder
-insertList [] = return "()" -- not convinced this can happen
-insertList xs = do
-  ls <- mapM (formatLabel ObjContext) xs
-  return $ mconcat ("( " : intersperse " " ls) `mappend` " )"
-    
-{-
 Add a blank node inline.
 -}
 
 insertBnode :: LabelContext -> RDFLabel -> Formatter B.Builder
 insertBnode SubjContext lbl = do
-  flag <- moreProperties
+  flag <- hasMore props
   txt <- if flag
          then (`mappend` "\n") `liftM` formatProperties lbl ""
          else return ""
@@ -469,59 +346,15 @@
   -- TODO: handle indentation?
   return $ mconcat ["[", txt, "]"]
 
-insertBnode _ lbl = do
-  ost <- get
-  let osubjs = subjs ost
-      oprops = props ost
-      oobjs  = objs  ost
-
-      (bsubj, rsubjs) = partition ((== lbl) . fst) osubjs
-
-      rprops = case bsubj of
-                 [(_,rs)] -> rs
-                 _ -> []
-
-      -- we essentially want to create a new subgraph
-      -- for this node but it's not as simple as that since
-      -- we could have something like
-      --     :a :b [ :foo [ :bar "xx" ] ]
-      -- so we still need to carry around the whole graph
-      --
-      nst = ost { subjs = rsubjs,
-                  props = rprops,
-                  objs  = []
-                }
-
-  put nst
-  flag <- moreProperties
-  txt <- if flag
-         then (`mappend` "\n") `liftM` formatProperties lbl ""
-         else return ""
-
-  -- TODO: how do we restore the original set up?
-  --       I can't believe the following is sufficient
-  --
-  nst' <- get
-  let slist  = map fst $ subjs nst'
-      nsubjs = filter (\(l,_) -> l `elem` slist) osubjs
-
-  put $ nst' { subjs = nsubjs,
-                       props = oprops, 
-                       objs  = oobjs
-             }
+insertBnode _ lbl = insertBnode_ subjs props objs updateState formatProperties lbl
 
-  -- TODO: handle indentation?
-  return $ mconcat ["[", txt, "]"]
-  
 ----------------------------------------------------------------------
 --  Formatting helpers
 ----------------------------------------------------------------------
 
 newState :: RDFGraph -> N3FormatterState -> N3FormatterState
 newState gr st = 
-    let ngs0 = nodeGenSt st
-        pre' = prefixes ngs0 `M.union` getNamespaces gr
-        ngs' = ngs0 { prefixes = pre' }
+    let pre' = prefixes st `M.union` getNamespaces gr
         (arcSubjs, bNodes) = processArcs gr
 
     in st  { graph     = gr
@@ -529,70 +362,38 @@
            , props     = []
            , objs      = []
            , formAvail = getFormulae gr
-           , nodeGenSt = ngs'
+           , prefixes  = pre'
            , bNodesCheck   = bNodes
            }
 
-setGraph :: RDFGraph -> Formatter ()
-setGraph = modify . newState
-
-hasMore :: (N3FormatterState -> [b]) -> Formatter Bool
-hasMore lens = (not . null . lens) `liftM` get
-
-moreSubjects :: Formatter Bool
-moreSubjects = hasMore subjs
-
-moreProperties :: Formatter Bool
-moreProperties = hasMore props
-
-moreObjects :: Formatter Bool
-moreObjects = hasMore objs
-
 nextSubject :: Formatter RDFLabel
-nextSubject = do
-  st <- get
-
-  let sb:sbs = subjs st
-      nst = st  { subjs = sbs
-                , props = snd sb
-                , objs  = []
-                }
-
-  put nst
-  return $ fst sb
+nextSubject = 
+    changeState $ \st -> 
+        let (a,b):sbs = subjs st
+            nst = st  { subjs = sbs
+                      , props = b
+                      , objs  = []
+                      }
+        in (a, nst)
 
 nextProperty :: RDFLabel -> Formatter RDFLabel
-nextProperty _ = do
-  st <- get
-
-  let pr:prs = props st
-      nst = st  { props = prs
-                 , objs  = snd pr
-                 }
-
-  put nst
-  return $ fst pr
-
+nextProperty _ =
+    changeState $ \st ->
+        let (a,b):prs = props st
+            nst = st  { props = prs
+                      , objs  = b
+                      }
+        in (a, nst)
+        
 nextObject :: RDFLabel -> RDFLabel -> Formatter RDFLabel
-nextObject _ _ = do
-  st <- get
-
-  let ob:obs = objs st
-      nst = st { objs = obs }
-
-  put nst
-  return ob
+nextObject _ _ =
+    changeState $ \st ->
+        let ob:obs = objs st
+            nst = st { objs = obs }
+        in (ob, nst)
 
 nextLine :: B.Builder -> Formatter B.Builder
-nextLine str = do
-  ind <- getIndent
-  brk <- getLineBreak
-  if brk
-    then return $ ind `mappend` str
-    else do
-      --  After first line, always insert line break
-      setLineBreak True
-      return str
+nextLine = nextLine_ indent _lineBreak
 
 --  Format a label
 --  Most labels are simply displayed as provided, but there are a
@@ -633,17 +434,19 @@
 
 {-
 The "[..]" conversion is done last, after "()" and "{}" checks.
+
+TODO: look at the (_:_) check on the blank string; why is this needed?
 -}
 formatLabel lctxt lab@(Blank (_:_)) = do
   mlst <- extractList lctxt lab
   case mlst of
-    Just lst -> insertList lst
+    Just lst -> insertList (formatLabel ObjContext) lst
     Nothing -> do
               mfml <- extractFormula lab
               case mfml of
                 Just fml -> insertFormula fml
                 Nothing -> do
-                          nb1 <- getBnodesCheck
+                          nb1 <- gets bNodesCheck
                           if lctxt /= PredContext && lab `notElem` nb1
                             then insertBnode lctxt lab
                             else formatNodeId lab
@@ -652,91 +455,23 @@
   case lookup sn specialTable of
     Just txt -> return $ quoteB True txt -- TODO: do we need to quote?
     Nothing -> do
-      pr <- getPrefixes
-      let nsuri  = getScopeURI sn
-          local  = getLName $ getScopeLocal sn
-	  prefix = findPrefix nsuri pr
-          
-          name   = case prefix of
-                     Just (Just p) -> B.fromText $ quoteT True $ mconcat [p, ":", local] -- TODO: what are quoting rules for QNames
-                     _ -> mconcat ["<", quoteB True (show nsuri ++ T.unpack local), ">"]
-      
-      {-
-          name   = case prefix of
-                     Just p -> quoteB True (p ++ ":" ++ local) -- TODO: what are quoting rules for QNames
-                     _ -> mconcat ["<", quoteB True (nsuri++local), ">"]
-      -}
-          
+      pr <- gets prefixes
       queueFormula lab
-      return name
+      return $ formatScopedName sn pr
 
--- The canonical notation for xsd:double in XSD, with an upper-case E,
--- does not match the syntax used in N3, so we need to convert here.     
--- Rather than converting back to a Double and then displaying that       
--- we just convert E to e for now.      
---
-formatLabel _ (TypedLit lit dtype)
-    | dtype == xsdDouble = return $ B.fromText $ T.toLower lit
-    | dtype `elem` [xsdBoolean, xsdDecimal, xsdInteger] = return $ B.fromText lit
-    | otherwise = return $ quoteText lit `mappend` "^^" `mappend` showScopedName dtype
-formatLabel _ (LangLit lit lcode) =
-    return $ quoteText lit `mappend` "@" `mappend` B.fromText (fromLangTag lcode)
-formatLabel _ (Lit lit) = return $ quoteText lit
+formatLabel _ (Lit lit)            = return $ formatPlainLit lit
+formatLabel _ (LangLit lit lcode)  = return $ formatLangLit lit lcode
+formatLabel _ (TypedLit lit dtype) = return $ formatTypedLit lit dtype
 
 formatLabel _ lab = return $ B.fromString $ show lab
 
-{-
-We have to decide whether to use " or """ to quote
-the string.
-
-There is also no need to restrict the string to the
-ASCII character set; this could be an option but we
-can also leave Unicode as is (or at least convert to UTF-8).
-
-If we use """ to surround the string then we protect the
-last character if it is a " (assuming it isn't protected).
--}
-
-quoteText :: T.Text -> B.Builder
-quoteText txt = 
-  let st = T.unpack txt -- TODO: fix
-      qst = quoteB (n==1) st
-      n = if '\n' `elem` st || '"' `elem` st then 3 else 1
-      qch = B.fromString (replicate n '"')
-  in mconcat [qch, qst, qch]
-
 formatNodeId :: RDFLabel -> Formatter B.Builder
 formatNodeId lab@(Blank (lnc:_)) =
     if isDigit lnc then mapBlankNode lab else return $ B.fromString $ show lab
 formatNodeId other = error $ "formatNodeId not expecting a " ++ show other -- to shut up -Wall
 
 mapBlankNode :: RDFLabel -> Formatter B.Builder
-mapBlankNode lab = do
-  ngs <- getNgs
-  let cmap = nodeMap ngs
-      cval = nodeGen ngs
-  nv <- case M.findWithDefault 0 lab cmap of
-    0 -> do 
-      let nval = succ cval
-          nmap = M.insert lab nval cmap
-      setNgs $ ngs { nodeGen = nval, nodeMap = nmap }
-      return nval
-      
-    n -> return n
-  
-  -- TODO: is this what we want?
-  return $ "_:swish" `mappend` B.fromString (show nv)
-
--- TODO: need to be a bit more clever with this than we did in NTriples
---       not sure the following counts as clever enough ...
---  
-showScopedName :: ScopedName -> B.Builder
-{-
-showScopedName (ScopedName n l) = 
-  let uri = nsURI n ++ l
-  in quote uri
--}
-showScopedName = quoteB True . show
+mapBlankNode = mapBlankNode_ _nodeGen
 
 --------------------------------------------------------------------------------
 --
diff --git a/src/Swish/RDF/Formatter/NTriples.hs b/src/Swish/RDF/Formatter/NTriples.hs
--- a/src/Swish/RDF/Formatter/NTriples.hs
+++ b/src/Swish/RDF/Formatter/NTriples.hs
@@ -29,7 +29,11 @@
     )
 where
 
-import Swish.RDF.Formatter.Internal (NodeGenLookupMap)
+import Swish.RDF.Formatter.Internal ( NodeGenState(..)
+                                    , SLens (..)
+                                    , emptyNgs
+                                    , mapBlankNode_
+                                    )
 
 import Swish.GraphClass (Arc(..))
 import Swish.Namespace (ScopedName, getQName)
@@ -44,13 +48,11 @@
 
 import Data.Char (ord, intToDigit, toUpper)
 import Data.Monoid
-import Data.Word (Word32)
 
 -- it strikes me that using Lazy Text here is likely to be
 -- wrong; however I have done no profiling to back this
 -- assumption up!
 
-import qualified Data.Map as M
 import qualified Data.Set as S
 import qualified Data.Text as T
 import qualified Data.Text.Lazy as L
@@ -62,18 +64,10 @@
 --
 --  This is a lot simpler than other formatters.
 
-data NTFormatterState = NTFS { 
-      ntfsNodeMap :: NodeGenLookupMap,
-      ntfsNodeGen :: Word32
-    } deriving Show
-
-emptyNTFS :: NTFormatterState
-emptyNTFS = NTFS {
-              ntfsNodeMap = M.empty,
-              ntfsNodeGen = 0
-              }
+type Formatter a = State NodeGenState a
 
-type Formatter a = State NTFormatterState a
+_nodeGen :: SLens NodeGenState NodeGenState
+_nodeGen = SLens id $ flip const
 
 -- | Convert a RDF graph to NTriples format.
 formatGraphAsText :: RDFGraph -> T.Text
@@ -85,7 +79,7 @@
 
 -- | Convert a RDF graph to NTriples format.
 formatGraphAsBuilder :: RDFGraph -> B.Builder
-formatGraphAsBuilder gr = fst $ runState (formatGraph gr) emptyNTFS
+formatGraphAsBuilder gr = fst $ runState (formatGraph gr) emptyNgs
 
 ----------------------------------------------------------------------
 --  Formatting as a monad-based computation
@@ -108,69 +102,27 @@
   pl <- formatLabel p
   ol <- formatLabel o
   return $ mconcat [sl, space, pl, space, ol, nl]
-  -- return $ sl `mappend` $ space `mappend` $ pl `mappend` $ space `mappend` $ ol `mappend` nl
   
-{-
-If we have a blank node then can
-
-  - use the label it contains
-  - generate a new one on output
-
-For now we create new labels whatever the input was since this
-simplifies things, but it may be changed.
-
-formatLabel :: RDFLabel -> Formatter String
-formatLabel lab@(Blank (lnc:_)) = 
-  if isDigit lnc then mapBlankNode lab else return $ show lab
-formatLabel lab = return $ show lab
--}
-
-squote, at, carets  :: B.Builder
-squote = "\""
-at     = "@"
-carets = "^^"
-
 formatLabel :: RDFLabel -> Formatter B.Builder
 formatLabel lab@(Blank _) = mapBlankNode lab
 formatLabel (Res sn) = return $ showScopedName sn
 formatLabel (Lit lit) = return $ quoteText lit
-formatLabel (LangLit lit lang) = return $ mconcat [quoteText lit, at, B.fromText (fromLangTag lang)]
-formatLabel (TypedLit lit dt)  = return $ mconcat [quoteText lit, carets, showScopedName dt]
+formatLabel (LangLit lit lang) = return $ mconcat [quoteText lit, "@", B.fromText (fromLangTag lang)]
+formatLabel (TypedLit lit dt)  = return $ mconcat [quoteText lit, "^^", showScopedName dt]
 
 -- do not expect to get the following, but include
 -- just in case rather than failing
 formatLabel lab = return $ B.fromString $ show lab
 
 mapBlankNode :: RDFLabel -> Formatter B.Builder
-mapBlankNode lab = do
-  st <- get
-  let cmap = ntfsNodeMap st
-      cval = ntfsNodeGen st
-
-  nv <- case M.findWithDefault 0 lab cmap of
-            0 -> do
-              let nval = succ cval
-                  nmap = M.insert lab nval cmap
-
-              put $ st { ntfsNodeMap = nmap, ntfsNodeGen = nval }
-              return nval
-
-            n -> return n
-
-  return $ "_:swish" `mappend` B.fromString (show nv)
+mapBlankNode = mapBlankNode_ _nodeGen
 
 -- TODO: can we use Network.URI to protect the URI?
 showScopedName :: ScopedName -> B.Builder
-{-
-showScopedName (ScopedName n l) = 
-  let uri = T.pack (show (nsURI n)) `mappend` l
-  in mconcat ["<", B.fromText (quote uri), ">"]
--}
--- showScopedName s = mconcat ["<", B.fromText (quote (T.pack (show (getQName s)))), ">"]
 showScopedName s = B.fromText (quote (T.pack (show (getQName s)))) -- looks like qname already adds the <> around this
 
 quoteText :: T.Text -> B.Builder
-quoteText  st = mconcat [squote, B.fromText (quote st), squote]
+quoteText  st = mconcat ["\"", B.fromText (quote st), "\""]
 
 {-
 QUS: should we be operating on Text like this?
diff --git a/src/Swish/RDF/Formatter/Turtle.hs b/src/Swish/RDF/Formatter/Turtle.hs
--- a/src/Swish/RDF/Formatter/Turtle.hs
+++ b/src/Swish/RDF/Formatter/Turtle.hs
@@ -40,44 +40,48 @@
     , formatGraphAsBuilder
     , formatGraphIndent  
     , formatGraphDiag
-      
-    --  -- * Auxillary routines
-    -- , quoteText
     )
 where
 
 import Swish.RDF.Formatter.Internal (NodeGenLookupMap, SubjTree, PredTree
+                                    , SLens(..)
                                     , LabelContext(..)
-                                    , NodeGenState(..), emptyNgs
+                                    , NodeGenState(..)
+                                    , changeState
+                                    , hasMore
+                                    , emptyNgs
                                     , findMaxBnode
-                                    , getCollection
                                     , processArcs
-				    , findPrefix)
-
-import Swish.Namespace (ScopedName, getScopeLocal, getScopeURI)
-import Swish.QName (getLName)
+                                    , formatScopedName
+                                    , formatPlainLit
+                                    , formatLangLit
+                                    , formatTypedLit
+                                    , insertList
+                                    , nextLine_
+                                    , mapBlankNode_
+                                    , formatPrefixes_
+                                    , formatGraph_
+                                    , formatSubjects_
+                                    , formatProperties_
+                                    , formatObjects_
+                                    , insertBnode_
+                                    , extractList_
+				    )
 
 import Swish.RDF.Graph (
   RDFGraph, RDFLabel(..)
   , NamespaceMap
+  , emptyNamespaceMap
   , getNamespaces
   , emptyRDFGraph
-  , quote
-  , quoteT
-  , resRdfFirst, resRdfRest
   )
 
-import Swish.RDF.Vocabulary ( fromLangTag 
-                            , rdfType
-                            , rdfNil
-                            , xsdBoolean, xsdDecimal, xsdInteger, xsdDouble 
-                            )
+import Swish.RDF.Vocabulary (rdfType, rdfNil)
 
-import Control.Monad (liftM, when)
-import Control.Monad.State (State, modify, get, put, runState)
+import Control.Monad (liftM)
+import Control.Monad.State (State, modify, gets, runState)
 
 import Data.Char (isDigit)
-import Data.List (partition, intersperse)
 import Data.Monoid (Monoid(..))
 import Data.Word (Word32)
 
@@ -90,10 +94,6 @@
 import qualified Data.Text.Lazy as L
 import qualified Data.Text.Lazy.Builder as B
 
--- temporary conversion
-quoteB :: Bool -> String -> B.Builder
-quoteB f v = B.fromString $ quote f v
-
 ----------------------------------------------------------------------
 --  Graph formatting state monad
 ----------------------------------------------------------------------
@@ -113,13 +113,30 @@
     , objs      :: [RDFLabel]          -- for last property selected
     -- , formAvail :: FormulaMap RDFLabel
     -- , formQueue :: [(RDFLabel,RDFGraph)]
+    , prefixes  :: NamespaceMap
     , nodeGenSt :: NodeGenState
     , bNodesCheck   :: [RDFLabel]      -- these bNodes are not to be converted to '[..]' format
     , traceBuf  :: [String]
     }
-             
+
+type SL a = SLens TurtleFormatterState a
+
+_lineBreak :: SL Bool
+_lineBreak = SLens lineBreak    $ \a b -> a { lineBreak = b }
+
+_nodeGen :: SL NodeGenState
+_nodeGen   = SLens nodeGenSt    $ \a b -> a { nodeGenSt = b }
+
 type Formatter a = State TurtleFormatterState a
 
+updateState ::
+    TurtleFormatterState
+    -> SubjTree RDFLabel
+    -> PredTree RDFLabel
+    -> [RDFLabel]
+    -> TurtleFormatterState
+updateState ost nsubjs nprops nobjs = ost { subjs = nsubjs, props = nprops, objs = nobjs }
+
 emptyTFS :: NodeGenState -> TurtleFormatterState
 emptyTFS ngs = TFS
     { indent    = "\n"
@@ -128,97 +145,33 @@
     , subjs     = []
     , props     = []
     , objs      = []
-    -- , formAvail = emptyFormulaMap
-    -- , formQueue = []
+    , prefixes  = emptyNamespaceMap
     , nodeGenSt = ngs
     , bNodesCheck   = []
     , traceBuf  = []
     }
 
-getIndent :: Formatter B.Builder
-getIndent = indent `liftM` get
-
 setIndent :: B.Builder -> Formatter ()
 setIndent ind = modify $ \st -> st { indent = ind }
 
-getLineBreak :: Formatter Bool
-getLineBreak = lineBreak `liftM` get
-
 setLineBreak :: Bool -> Formatter ()
 setLineBreak brk = modify $ \st -> st { lineBreak = brk }
 
-getNgs :: Formatter NodeGenState
-getNgs = nodeGenSt `liftM` get
-
-setNgs :: NodeGenState -> Formatter ()
-setNgs ngs = modify $ \st -> st { nodeGenSt = ngs }
-
-getPrefixes :: Formatter NamespaceMap
-getPrefixes = prefixes `liftM` getNgs
-
-getSubjs :: Formatter (SubjTree RDFLabel)
-getSubjs = subjs `liftM` get
-
 setSubjs :: SubjTree RDFLabel -> Formatter ()
 setSubjs sl = modify $ \st -> st { subjs = sl }
 
-getProps :: Formatter (PredTree RDFLabel)
-getProps = props `liftM` get
-
 setProps :: PredTree RDFLabel -> Formatter ()
 setProps ps = modify $ \st -> st { props = ps }
 
 {-
-getObjs :: Formatter ([RDFLabel])
-getObjs = objs `liftM` get
-
-setObjs :: [RDFLabel] -> Formatter ()
-setObjs os = do
-  st <- get
-  put $ st { objs = os }
--}
-
-getBnodesCheck :: Formatter [RDFLabel]
-getBnodesCheck = bNodesCheck `liftM` get
-
-{-
-addTrace :: String -> Formatter ()
-addTrace tr = do
-  st <- get
-  put $ st { traceBuf = tr : traceBuf st }
--}
-  
-{-
 TODO:
 
 Should we change the preds/objs entries as well?
 
 -}
 extractList :: LabelContext -> RDFLabel -> Formatter (Maybe [RDFLabel])
-extractList lctxt ln = do
-  osubjs <- getSubjs
-  oprops <- getProps
-  let mlst = getCollection osubjs' ln
-
-      -- we only want to send in rdf:first/rdf:rest here
-      fprops = filter ((`elem` [resRdfFirst, resRdfRest]) . fst) oprops
-
-      osubjs' =
-          case lctxt of
-            SubjContext -> (ln, fprops) : osubjs
-            _ -> osubjs 
-
-      -- tr = "extractList " ++ show ln ++ " (" ++ show lctxt ++ ")\n -> osubjs= " ++ show osubjs ++ "\n -> opreds= " ++ show oprops ++ "\n -> mlst= " ++ show mlst ++ "\n"
-  -- addTrace tr
-  case mlst of
-    -- sl is guaranteed to be free of (ln,fprops) here if lctxt is SubjContext
-    Just (sl,ls,_) -> do
-              setSubjs sl
-              when (lctxt == SubjContext) $ setProps $ filter ((`notElem` [resRdfFirst, resRdfRest]) . fst) oprops
-              return (Just ls)
+extractList = extractList_ subjs props setSubjs setProps
 
-    Nothing -> return Nothing
-  
 ----------------------------------------------------------------------
 --  Define a top-level formatter function:
 ----------------------------------------------------------------------
@@ -253,10 +206,7 @@
   -> (B.Builder, NodeGenLookupMap, Word32, [String])
 formatGraphDiag indnt flag gr = 
   let fg  = formatGraph indnt " .\n" False flag gr
-      ngs = emptyNgs {
-        prefixes = M.empty,
-        nodeGen  = findMaxBnode gr
-        }
+      ngs = emptyNgs { nodeGen = findMaxBnode gr }
              
       (out, fgs) = runState fg (emptyTFS ngs)
       ogs        = nodeGenSt fgs
@@ -274,253 +224,81 @@
   -> Bool       -- True if prefix strings are to be generated
   -> RDFGraph   -- graph to convert
   -> Formatter B.Builder
-formatGraph ind end dobreak dopref gr = do
-  setIndent ind
-  setLineBreak dobreak
-  setGraph gr
-  
-  fp <- if dopref
-        then formatPrefixes (getNamespaces gr)
-        else return mempty
-  more <- moreSubjects
-  if more
-    then do
-      fr <- formatSubjects
-      return $ mconcat [fp, fr, end]
-    else return fp
+formatGraph = formatGraph_ setIndent setLineBreak newState formatPrefixes subjs formatSubjects
 
 formatPrefixes :: NamespaceMap -> Formatter B.Builder
-formatPrefixes pmap = do
-  let mls = map pref $ M.assocs pmap
-  ls <- sequence mls
-  return $ mconcat ls
-    where
-      pref (Just p,u) = nextLine $ mconcat ["@prefix ", B.fromText p, ": <", quoteB True (show u), "> ."]
-      pref (_,u)      = nextLine $ mconcat ["@prefix : <", quoteB True (show u), "> ."]
-
-{-
-NOTE:
-I expect there to be confusion below where I need to
-convert from Text to Builder
--}
+formatPrefixes = formatPrefixes_ nextLine
 
 formatSubjects :: Formatter B.Builder
-formatSubjects = do
-  sb    <- nextSubject
-  sbstr <- formatLabel SubjContext sb
-  
-  flagP <- moreProperties
-  if flagP
-    then do
-      prstr <- formatProperties sb sbstr
-      flagS <- moreSubjects
-      if flagS
-        then do
-          fr <- formatSubjects
-          return $ mconcat [prstr, " .", fr]
-        else return prstr
-           
-    else do
-      txt <- nextLine sbstr
-    
-      flagS <- moreSubjects
-      if flagS
-        then do
-          fr <- formatSubjects
-          return $ mconcat [txt, " .", fr]
-        else return txt
-
-{-
-TODO: now we are throwing a Builder around it is awkward to
-get the length of the text to calculate the indentation
-
-So
-
-  a) change the indentation scheme
-  b) pass around text instead of builder
-
-mkIndent :: L.Text -> L.Text
-mkIndent inVal = L.replicate (L.length inVal) " "
--}
-
-hackIndent :: B.Builder
-hackIndent = "    "
+formatSubjects = formatSubjects_ nextSubject formatLabel props formatProperties subjs nextLine
 
 formatProperties :: RDFLabel -> B.Builder -> Formatter B.Builder
-formatProperties sb sbstr = do
-  pr <- nextProperty sb
-  prstr <- formatLabel PredContext pr
-  obstr <- formatObjects sb pr $ mconcat [sbstr, " ", prstr]
-  more  <- moreProperties
-  let sbindent = hackIndent -- mkIndent sbstr
-  if more
-    then do
-      fr <- formatProperties sb sbindent
-      nl <- nextLine $ obstr `mappend` " ;"
-      return $ nl `mappend` fr
-    else nextLine obstr
+formatProperties = formatProperties_ nextProperty formatLabel formatObjects props nextLine
 
 formatObjects :: RDFLabel -> RDFLabel -> B.Builder -> Formatter B.Builder
-formatObjects sb pr prstr = do
-  ob    <- nextObject sb pr
-  obstr <- formatLabel ObjContext ob
-  more  <- moreObjects
-  if more
-    then do
-      let prindent = hackIndent -- mkIndent prstr
-      fr <- formatObjects sb pr prindent
-      nl <- nextLine $ mconcat [prstr, " ", obstr, ","]
-      return $ nl `mappend` fr
-    else return $ mconcat [prstr, " ", obstr]
+formatObjects = formatObjects_ nextObject formatLabel objs nextLine
 
 {-
-Add a list inline. We are given the labels that constitute
-the list, in order, so just need to display them surrounded
-by ().
--}
-insertList :: [RDFLabel] -> Formatter B.Builder
-insertList [] = return "()" -- not convinced this can happen
-insertList xs = do
-  ls <- mapM (formatLabel ObjContext) xs
-  return $ mconcat ("( " : intersperse " " ls) `mappend` " )"
-    
-{-
 Add a blank node inline.
 -}
 
 insertBnode :: LabelContext -> RDFLabel -> Formatter B.Builder
 insertBnode SubjContext lbl = do
   -- a safety check
-  flag <- moreProperties
+  flag <- hasMore props
   if flag
     then do
       txt <- (`mappend` "\n") `liftM` formatProperties lbl ""
       return $ mconcat ["[] ", txt]
     else error $ "Internal error: expected properties with label: " ++ show lbl
 
-insertBnode _ lbl = do
-  ost <- get
-  let osubjs = subjs ost
-      oprops = props ost
-      oobjs  = objs  ost
-
-      (bsubj, rsubjs) = partition ((== lbl) . fst) osubjs
-
-      rprops = case bsubj of
-                 [(_,rs)] -> rs
-                 _ -> []
-
-      -- we essentially want to create a new subgraph
-      -- for this node but it's not as simple as that since
-      -- we could have something like
-      --     :a :b [ :foo [ :bar "xx" ] ]
-      -- so we still need to carry around the whole graph
-      --
-      nst = ost { subjs = rsubjs,
-                  props = rprops,
-                  objs  = []
-                }
-
-  put nst
-  flag <- moreProperties
-  txt <- if flag
-         then (`mappend` "\n") `liftM` formatProperties lbl ""
-         else return ""
-
-  -- TODO: how do we restore the original set up?
-  --       I can't believe the following is sufficient
-  --
-  nst' <- get
-  let slist  = map fst $ subjs nst'
-      nsubjs = filter (\(l,_) -> l `elem` slist) osubjs
-
-  put $ nst' { subjs = nsubjs,
-                       props = oprops, 
-                       objs  = oobjs
-             }
+insertBnode _ lbl = insertBnode_ subjs props objs updateState formatProperties lbl
 
-  -- TODO: handle indentation?
-  return $ mconcat ["[", txt, "]"]
-  
 ----------------------------------------------------------------------
 --  Formatting helpers
 ----------------------------------------------------------------------
 
 newState :: RDFGraph -> TurtleFormatterState -> TurtleFormatterState
 newState gr st = 
-    let ngs0 = nodeGenSt st
-        pre' = prefixes ngs0 `M.union` getNamespaces gr
-        ngs' = ngs0 { prefixes = pre' }
+    let pre' = prefixes st `M.union` getNamespaces gr
         (arcSubjs, bNodes) = processArcs gr
 
     in st  { graph     = gr
            , subjs     = arcSubjs
            , props     = []
            , objs      = []
-           , nodeGenSt = ngs'
+           , prefixes  = pre'
            , bNodesCheck   = bNodes
            }
 
-setGraph :: RDFGraph -> Formatter ()
-setGraph = modify . newState
-
-hasMore :: (TurtleFormatterState -> [b]) -> Formatter Bool
-hasMore lens = (not . null . lens) `liftM` get
-
-moreSubjects :: Formatter Bool
-moreSubjects = hasMore subjs
-
-moreProperties :: Formatter Bool
-moreProperties = hasMore props
-
-moreObjects :: Formatter Bool
-moreObjects = hasMore objs
-
 nextSubject :: Formatter RDFLabel
-nextSubject = do
-  st <- get
-
-  let sb:sbs = subjs st
-      nst = st  { subjs = sbs
-                , props = snd sb
-                , objs  = []
-                }
-
-  put nst
-  return $ fst sb
+nextSubject = 
+    changeState $ \st -> 
+        let (a,b):sbs = subjs st
+            nst = st  { subjs = sbs
+                      , props = b
+                      , objs  = []
+                      }
+        in (a, nst)
 
 nextProperty :: RDFLabel -> Formatter RDFLabel
-nextProperty _ = do
-  st <- get
-
-  let pr:prs = props st
-      nst = st  { props = prs
-                 , objs  = snd pr
-                 }
-
-  put nst
-  return $ fst pr
-
+nextProperty _ =
+    changeState $ \st ->
+        let (a,b):prs = props st
+            nst = st  { props = prs
+                      , objs  = b
+                      }
+        in (a, nst)
+        
 nextObject :: RDFLabel -> RDFLabel -> Formatter RDFLabel
-nextObject _ _ = do
-  st <- get
-
-  let ob:obs = objs st
-      nst = st { objs = obs }
-
-  put nst
-  return ob
+nextObject _ _ =
+    changeState $ \st ->
+        let ob:obs = objs st
+            nst = st { objs = obs }
+        in (ob, nst)
 
 nextLine :: B.Builder -> Formatter B.Builder
-nextLine str = do
-  ind <- getIndent
-  brk <- getLineBreak
-  if brk
-    then return $ ind `mappend` str
-    else do
-      --  After first line, always insert line break
-      setLineBreak True
-      return str
+nextLine = nextLine_ indent _lineBreak
 
 --  Format a label
 --  Most labels are simply displayed as provided, but there are a
@@ -550,16 +328,18 @@
 
 {-
 The "[..]" conversion is done last, after "()" and "{}" checks.
+
+TODO: look at the (_:_) check on the blank string; why is this needed?
 -}
 formatLabel lctxt lab@(Blank (_:_)) = do
   mlst <- extractList lctxt lab
   case mlst of
-    Just lst -> insertList lst
+    Just lst -> insertList (formatLabel ObjContext) lst
     Nothing -> do
       -- NOTE: unlike N3 we do not properly handle "formula"/named graphs
       -- also we only expand out bnodes into [...] format when it's a object.
       -- although we need to handle [] for the subject.
-      nb1 <- getBnodesCheck
+      nb1 <- gets bNodesCheck
       if lctxt /= PredContext && lab `notElem` nb1
         then insertBnode lctxt lab
         else formatNodeId lab
@@ -569,75 +349,22 @@
   | ctxt == PredContext && sn == rdfType = return "a"
   | ctxt == ObjContext  && sn == rdfNil  = return "()"
   | otherwise = do
-  pr <- getPrefixes
-  let nsuri  = getScopeURI sn
-      local  = getLName $ getScopeLocal sn
-      name   = case findPrefix nsuri pr of
-        Just (Just p) -> B.fromText $ quoteT True $ mconcat [p, ":", local] -- TODO: what are quoting rules for QNames
-        _ -> mconcat ["<", quoteB True (show nsuri ++ T.unpack local), ">"]
-      
-  return name
+  pr <- gets prefixes
+  return $ formatScopedName sn pr
 
--- The canonical notation for xsd:double in XSD, with an upper-case E,
--- does not match the syntax used in N3, so we need to convert here.     
--- Rather than converting back to a Double and then displaying that       
--- we just convert E to e for now.      
---      
-formatLabel _ (Lit lit) = return $ quoteText lit
-formatLabel _ (LangLit lit lcode) = return $ quoteText lit `mappend` "@" `mappend` B.fromText (fromLangTag lcode)
-formatLabel _ (TypedLit lit dtype)
-    | dtype == xsdDouble = return $ B.fromText $ T.toLower lit
-    | dtype `elem` [xsdBoolean, xsdDecimal, xsdInteger] = return $ B.fromText lit
-    | otherwise = return $ quoteText lit `mappend` "^^" `mappend` showScopedName dtype
+formatLabel _ (Lit lit) = return $ formatPlainLit lit
+formatLabel _ (LangLit lit lcode) = return $ formatLangLit lit lcode
+formatLabel _ (TypedLit lit dtype) = return $ formatTypedLit lit dtype
 
 formatLabel _ lab = return $ B.fromString $ show lab
 
-{-|
-Convert text into a format for display in Turtle. The idea
-is to use one double quote unless three are needed, and to
-handle adding necessary @\\@ characters, or conversion
-for Unicode characters.
--}
-quoteText :: T.Text -> B.Builder
-quoteText txt = 
-  let st = T.unpack txt -- TODO: fix
-      qst = quoteB (n==1) st
-      n = if '\n' `elem` st || '"' `elem` st then 3 else 1
-      qch = B.fromString (replicate n '"')
-  in mconcat [qch, qst, qch]
-
 formatNodeId :: RDFLabel -> Formatter B.Builder
 formatNodeId lab@(Blank (lnc:_)) =
     if isDigit lnc then mapBlankNode lab else return $ B.fromString $ show lab
 formatNodeId other = error $ "formatNodeId not expecting a " ++ show other -- to shut up -Wall
 
 mapBlankNode :: RDFLabel -> Formatter B.Builder
-mapBlankNode lab = do
-  ngs <- getNgs
-  let cmap = nodeMap ngs
-      cval = nodeGen ngs
-  nv <- case M.findWithDefault 0 lab cmap of
-    0 -> do 
-      let nval = succ cval
-          nmap = M.insert lab nval cmap
-      setNgs $ ngs { nodeGen = nval, nodeMap = nmap }
-      return nval
-      
-    n -> return n
-  
-  -- TODO: is this what we want?
-  return $ "_:swish" `mappend` B.fromString (show nv)
-
--- TODO: need to be a bit more clever with this than we did in NTriples
---       not sure the following counts as clever enough ...
---  
-showScopedName :: ScopedName -> B.Builder
-{-
-showScopedName (ScopedName n l) = 
-  let uri = nsURI n ++ l
-  in quote uri
--}
-showScopedName = quoteB True . show
+mapBlankNode = mapBlankNode_ _nodeGen
 
 --------------------------------------------------------------------------------
 --
diff --git a/src/Swish/RDF/Graph.hs b/src/Swish/RDF/Graph.hs
--- a/src/Swish/RDF/Graph.hs
+++ b/src/Swish/RDF/Graph.hs
@@ -1153,19 +1153,21 @@
 instance (Label lb) => Monoid (NSGraph lb) where
     mempty  = emptyGraph
     mappend = merge
+
+-- fmapNSGraph :: (Ord lb1, Ord lb2) => (lb1 -> lb2) -> NSGraph lb1 -> NSGraph lb2
   
 -- | 'fmap' for 'NSGraph' instances.
--- fmapNSGraph :: (Ord lb1, Ord lb2) => (lb1 -> lb2) -> NSGraph lb1 -> NSGraph lb2
 fmapNSGraph :: (Ord lb) => (lb -> lb) -> NSGraph lb -> NSGraph lb
 fmapNSGraph f (NSGraph ns fml stmts) = 
     NSGraph ns (fmapFormulaMap f fml) ((S.map $ fmap f) stmts)
 
--- | 'Data.Traversable.traverse' for 'NSGraph' instances.
 {-
 traverseNSGraph :: 
     (Applicative f, Ord a, Ord b) 
     => (a -> f b) -> NSGraph a -> f (NSGraph b)
 -}
+
+-- | 'Data.Traversable.traverse' for 'NSGraph' instances.
 traverseNSGraph :: 
     (Applicative f, Ord a) 
     => (a -> f a) -> NSGraph a -> f (NSGraph a)
diff --git a/swish.cabal b/swish.cabal
--- a/swish.cabal
+++ b/swish.cabal
@@ -1,5 +1,5 @@
 Name:               swish
-Version:            0.8.0.0
+Version:            0.8.0.2
 Stability:          experimental
 License:            LGPL
 License-file:       LICENSE 
@@ -44,6 +44,17 @@
   .
   * Complete, ready-to-run, command-line and script-driven programs.
   .
+  Changes in version @0.8.0.2@:
+  .
+  * Restrict @hashable@ since tests fail with @1.1.2.5@
+  (this is a hack).
+  .
+  * Updated @directory@ constraint to include @1.2@ on ghc 7.6.
+  .
+  Changes in version @0.8.0.1@:
+  .
+  * Internal changes to Turtle/N3 formatting. No user-visible changes.
+  .
   Changes in version @0.8.0.0@:
   .
   * The @LDGraph@ class now uses @Set (Arc lb)@, rather than @[Arc lb]@,
@@ -101,9 +112,9 @@
       base >=3 && < 5,
       binary == 0.5.*,
       containers >= 0.4 && < 0.6,
-      directory >= 1.0 && < 1.2,
       filepath >= 1.1 && < 1.4,
-      hashable == 1.1.*,
+      -- hashable == 1.1.*,
+      hashable >= 1.1 && < 1.1.2.4,
       mtl >= 2 && < 3,
       network >= 2.2 && < 2.4,
       old-locale == 1.0.*, 
@@ -116,6 +127,11 @@
       Build-Depends:   intern == 0.8
    if impl(ghc >= 7.4.0)
       Build-Depends:   intern >= 0.8 && < 1.0
+
+   if impl(ghc < 7.6.0)
+      Build-Depends:   directory >= 1.0 && < 1.2
+   if impl(ghc >= 7.6.0)
+      Build-Depends:   directory >= 1.0 && < 1.3
 
    Hs-Source-Dirs: src/
    Other-Modules:  Swish.RDF.Formatter.Internal
