    -- | Define a position datatype for giving locations in error messages.
    module Text.XML.HaXml.Posn
      (
      -- * Position type
        Posn()
      -- ** Constructors of a new position
      , posInNewCxt    -- :: String -> Maybe Posn -> Posn
      , noPos          -- :: Posn
      -- ** Strictifier
      , forcep
      -- ** Modifiers
      , addcol, newline, tab, white
      -- ** Accessors
      , posnFilename, posnLine, posnColumn
      ) where

    import Char

    -- | Source positions contain a filename, line, column, and an
    --   inclusion point, which is itself another source position,
    --   recursively.
    data Posn = Pn String !Int !Int (Maybe Posn)
            deriving (Eq)

    posnFilename :: Posn -> FilePath
    posnFilename (Pn f _ _ _) = f

    posnLine, posnColumn :: Posn -> Int
    posnLine   (Pn _ x _ _) = x
    posnColumn (Pn _ _ x _) = x

    -- | Dummy value for generated data, where a true source position does
    --   not exist.
    noPos :: Posn
    noPos = Pn "no recorded position" 0 0 Nothing

    -- | @posInNewCxt name pos@ creates a new source position from an old one.
    --   It is used when opening a new file (e.g. a DTD inclusion), to denote
    --   the start of the file @name@, but retain the stacked information that
    --   it was included from the old @pos@.
    posInNewCxt :: String -> Maybe Posn -> Posn
    posInNewCxt name pos = Pn name 1 1 pos

    instance Show Posn where
          showsPrec _ (Pn f l c i) = showString "file " .
                                     showString f .
                                     showString "  at line " . shows l .
                                     showString " col " . shows c .
                                     ( case i of
                                        Nothing -> id
                                        Just p  -> showString "\n    used by  " .
                                                   shows p )

    -- | Just used to strictify the internal values of a position, to avoid
    --   space leaks.
    forcep :: Posn -> Int
    forcep (Pn _ n m _) = m `seq` n

    -- | Add n character positions to the given position.
    addcol :: Int -> Posn -> Posn
    addcol n (Pn f r c i) = Pn f r (c+n) i

    -- | Add a newline or tab to the given position.
    newline, tab :: Posn -> Posn
    newline (Pn f r _ i) = Pn f (r+1) 1 i
    tab     (Pn f r c i) = Pn f r (((c`div`8)+1)*8) i

    -- | Add the given whitespace char to the given position.
    --   Precondition: @white c | isSpace c = True@
    white :: Char -> Posn -> Posn
    white ' '  = addcol 1
    white '\n' = newline
    white '\r' = id
    white '\t' = tab
    white '\xa0' = addcol 1
    white _    = error "precondition not satisfied: Posn.white c | isSpace c"