    -- ------------------------------------------------------------

    {- |
       Module     : Text.XML.HXT.Arrow.XmlIOStateArrow
       Copyright  : Copyright (C) 2010 Uwe Schmidt
       License    : MIT

       Maintainer : Uwe Schmidt (uwe@fh-wedel.de)
       Stability  : stable
       Portability: portable

       the basic state arrows for XML processing

       A state is needed for global processing options,
       like encoding options, document base URI, trace levels
       and error message handling

       The state is separated into a user defined state
       and a system state. The system state contains variables
       for error message handling, for tracing, for the document base
       for accessing XML documents with relative references, e.g. DTDs,
       and a global key value store. This assoc list has strings as keys
       and lists of XmlTrees as values. It is used to store arbitrary
       XML and text values, e.g. user defined global options.

       The user defined part of the store is in the default case empty, defined as ().
       It can be extended with an arbitray data type

    -}

    -- ------------------------------------------------------------

    module Text.XML.HXT.Arrow.XmlIOStateArrow
        ( -- * Data Types
          XIOState(..),
          XIOSysState(..),
          IOStateArrow,
          IOSArrow,

          -- * Running Arrows
          initialState,
          initialSysState,
          runX,

          -- * User State Manipulation
          getUserState,
          setUserState,
          changeUserState,
          withExtendedUserState,
          withOtherUserState,

          -- * Global System State Access
          getSysParam,
          changeSysParam,

          setParamList,
          setParam,
          unsetParam,
          getParam,
          getAllParams,
          getAllParamsString,
          setParamString,
          getParamString,
          setParamInt,
          getParamInt,

          -- * Error Message Handling
          clearErrStatus,
          setErrStatus,
          getErrStatus,
          setErrMsgStatus,
          setErrorMsgHandler,

          errorMsgStderr,
          errorMsgCollect,
          errorMsgStderrAndCollect,
          errorMsgIgnore,

          getErrorMessages,

          filterErrorMsg,
          issueWarn,
          issueErr,
          issueFatal,
          setDocumentStatus,
          setDocumentStatusFromSystemState,
          documentStatusOk,

          -- * Document Base
          setBaseURI,
          getBaseURI,
          changeBaseURI,
          setDefaultBaseURI,
          getDefaultBaseURI,
          runInLocalURIContext,

          -- * Tracing
          setTraceLevel,
          getTraceLevel,
          withTraceLevel,
          setTraceCmd,
          getTraceCmd,
          trace,
          traceMsg,
          traceValue,
          traceString,
          traceSource,
          traceTree,
          traceDoc,
          traceState,

          -- * URI Manipulation
          expandURIString,
          expandURI,
          mkAbsURI,

          getFragmentFromURI,
          getPathFromURI,
          getPortFromURI,
          getQueryFromURI,
          getRegNameFromURI,
          getSchemeFromURI,
          getUserInfoFromURI,

          -- * Mime Type Handling
          setMimeTypeTable,
          setMimeTypeTableFromFile
          )
    where

    import Control.Arrow                            -- arrow classes
    import Control.Arrow.ArrowList
    import Control.Arrow.ArrowIf
    import Control.Arrow.ArrowTree
    import Control.Arrow.ArrowIO
    import Control.Arrow.IOStateListArrow

    import Control.Monad                    ( mzero
                                            , mplus )
    import Control.DeepSeq

    import Text.XML.HXT.DOM.Interface
    import Text.XML.HXT.Arrow.XmlArrow

    import Text.XML.HXT.Arrow.Edit          ( addHeadlineToXmlDoc
                                            , treeRepOfXmlDoc
                                            , indentDoc
                                            )

    import Data.Maybe

    import Network.URI                      ( URI
                                            , escapeURIChar
                                            , isUnescapedInURI
                                            , nonStrictRelativeTo
                                            , parseURIReference
                                            , uriAuthority
                                            , uriFragment
                                            , uriPath
                                            , uriPort
                                            , uriQuery
                                            , uriRegName
                                            , uriScheme
                                            , uriUserInfo
                                            )

    import System.IO                        ( hPutStrLn
                                            , hFlush
                                            , stderr
                                            )

    import System.Directory                 ( getCurrentDirectory )

    -- ------------------------------------------------------------
    {- $datatypes -}

    -- |
    -- predefined system state data type with all components for the
    -- system functions, like trace, error handling, ...

    data XIOSysState        = XIOSys  { xio_trace                   :: ! Int
                                      , xio_traceCmd                ::   Int -> String -> IO ()
                                      , xio_errorStatus             :: ! Int
                                      , xio_errorModule             :: ! String
                                      , xio_errorMsgHandler         ::   String -> IO ()
                                      , xio_errorMsgCollect         :: ! Bool
                                      , xio_errorMsgList            :: ! XmlTrees
                                      , xio_baseURI                 :: ! String
                                      , xio_defaultBaseURI          :: ! String
                                      , xio_attrList                :: ! (AssocList String XmlTrees)
                                      , xio_mimeTypes               ::   MimeTypeTable
                                      }

    instance NFData XIOSysState where
        rnf (XIOSys tr _trc es em _emh emc eml bu du al _mt)
            = rnf tr `seq` rnf es `seq` rnf em `seq` rnf emc `seq` rnf eml `seq` rnf bu `seq` rnf du `seq` rnf al

    -- |
    -- state datatype consists of a system state and a user state
    -- the user state is not fixed

    data XIOState us        = XIOState { xio_sysState               :: ! XIOSysState
                                       , xio_userState              :: ! us
                                       }

    instance (NFData us) => NFData (XIOState us) where
        rnf (XIOState sys usr)      = rnf sys `seq` rnf usr

    -- |
    -- The arrow type for stateful arrows

    type IOStateArrow s b c = IOSLA (XIOState s) b c

    -- |
    -- The arrow for stateful arrows with no user defined state

    type IOSArrow b c       = IOStateArrow () b c

    -- ------------------------------------------------------------

    -- | the default global state, used as initial state when running an 'IOSArrow' with 'runIOSLA' or
    -- 'runX'

    initialState    :: us -> XIOState us
    initialState s  = XIOState { xio_sysState       = initialSysState
                               , xio_userState      = s
                               }

    initialSysState :: XIOSysState
    initialSysState = XIOSys { xio_trace            = 0
                             , xio_traceCmd         = traceOutputToStderr
                             , xio_errorStatus      = c_ok
                             , xio_errorModule      = ""
                             , xio_errorMsgHandler  = hPutStrLn stderr
                             , xio_errorMsgCollect  = False
                             , xio_errorMsgList     = []
                             , xio_baseURI          = ""
                             , xio_defaultBaseURI   = ""
                             , xio_attrList         = []
                             , xio_mimeTypes        = defaultMimeTypeTable
                             }

    -- ------------------------------------------------------------

    -- |
    -- apply an 'IOSArrow' to an empty root node with 'initialState' () as initial state
    --
    -- the main entry point for running a state arrow with IO
    --
    -- when running @ runX f@ an empty XML root node is applied to @f@.
    -- usually @f@ will start with a constant arrow (ignoring the input), e.g. a 'Text.XML.HXT.Arrow.ReadDocument.readDocument' arrow.
    --
    -- for usage see examples with 'Text.XML.HXT.Arrow.WriteDocument.writeDocument'
    --
    -- if input has to be feed into the arrow use 'Control.Arrow.IOStateListArrow.runIOSLA' like in @ runIOSLA f emptyX inputDoc @

    runX            :: IOSArrow XmlTree c -> IO [c]
    runX            = runXIOState (initialState ())

    runXIOState     :: XIOState s -> IOStateArrow s XmlTree c -> IO [c]
    runXIOState s0 f
        = do
          (_finalState, res) <- runIOSLA (emptyRoot >>> f) s0 undefined
          return res
        where
        emptyRoot    = root [] []

    -- ------------------------------------------------------------

    {- user state -}

    -- | read the user defined part of the state

    getUserState    :: IOStateArrow s b s
    getUserState
        = IOSLA $ \ s _ ->
          return (s, [xio_userState s])

    -- | change the user defined part of the state

    changeUserState         :: (b -> s -> s) -> IOStateArrow s b b
    changeUserState cf
        = IOSLA $ \ s v ->
          let s' = s { xio_userState = cf v (xio_userState s) }
          in return (s', [v])

    -- | set the user defined part of the state

    setUserState            :: IOStateArrow s s s
    setUserState
        = changeUserState const

    -- | extend user state
    --
    -- Run an arrow with an extended user state component, The old component
    -- is stored together with a new one in a pair, the arrow is executed with this
    -- extended state, and the augmented state component is removed form the state
    -- when the arrow has finished its execution

    withExtendedUserState   :: s1 -> IOStateArrow (s1, s0) b c -> IOStateArrow s0 b c
    withExtendedUserState initS1 f
        = IOSLA $ \ s0 x ->
          do
          ~(finalS, res) <- runIOSLA f ( XIOState { xio_sysState  =          xio_sysState  s0
                                                  , xio_userState = (initS1, xio_userState s0)
                                                  }
                                       ) x
          return ( XIOState { xio_sysState  =      xio_sysState  finalS
                            , xio_userState = snd (xio_userState finalS)
                            }
                 , res
                 )

    -- | change the type of user state
    --
    -- This conversion is useful, when running a state arrow with another
    -- structure of the user state, e.g. with () when executing some IO arrows

    withOtherUserState      :: s1 -> IOStateArrow s1 b c -> IOStateArrow s0 b c
    withOtherUserState s1 f
        = IOSLA $ \ s x ->
          do
          (s', res) <- runIOSLA f ( XIOState { xio_sysState  = xio_sysState s
                                             , xio_userState = s1
                                             }
                                  ) x
          return ( XIOState { xio_sysState  = xio_sysState  s'
                            , xio_userState = xio_userState s
                            }
                 , res
                 )

    -- ------------------------------------------------------------

    {- $system state params -}

    getSysParam     :: (XIOSysState -> c) -> IOStateArrow s b c
    getSysParam f
        = IOSLA $ \ s _x ->
          return (s, (:[]) . f . xio_sysState $ s)

    changeSysParam          :: (b -> XIOSysState -> XIOSysState) -> IOStateArrow s b b
    changeSysParam cf
        = ( IOSLA $ \ s v ->
            let s' = changeSysState (cf v) s
            in return (s', [v])
          )
        where
        changeSysState css s = s { xio_sysState = css (xio_sysState s) }

    -- | store a single XML tree in global state under a given attribute name

    setParam        :: String -> IOStateArrow s XmlTree XmlTree
    setParam n
        = (:[]) ^>> setParamList n

    -- | store a list of XML trees in global system state under a given attribute name

    setParamList    :: String -> IOStateArrow s XmlTrees XmlTree
    setParamList n
        = changeSysParam addE
          >>>
          arrL id
        where
        addE x s = s { xio_attrList = addEntry n x (xio_attrList s) }

    -- | remove an entry in global state, arrow input remains unchanged

    unsetParam      :: String -> IOStateArrow s b b
    unsetParam n
        = changeSysParam delE
        where
        delE _ s = s { xio_attrList = delEntry n (xio_attrList s) }

    -- | read an attribute value from global state

    getParam        :: String -> IOStateArrow s b XmlTree
    getParam n
        = getAllParams
          >>>
          arrL (lookup1 n)

    -- | read all attributes from global state

    getAllParams    :: IOStateArrow s b (AssocList String XmlTrees)
    getAllParams
        = getSysParam xio_attrList

    -- | read all attributes from global state
    -- and convert the values to strings

    getAllParamsString      :: IOStateArrow s b (AssocList String String)
    getAllParamsString
        = getAllParams
          >>>
          listA ( unlistA
                  >>>
                  second (xshow unlistA)
                )

    setParamString  :: String -> String -> IOStateArrow s b b
    setParamString n v
        = perform ( txt v
                    >>>
                    setParam n
                  )

    -- | read a string value from global state,
    -- if parameter not set \"\" is returned

    getParamString  :: String -> IOStateArrow s b String
    getParamString n
        = xshow (getParam n)

    -- | store an int value in global state

    setParamInt     :: String -> Int -> IOStateArrow s b b
    setParamInt n v
        = setParamString n (show v)

    -- | read an int value from global state
    --
    -- > getParamInt 0 myIntAttr

    getParamInt     :: Int -> String -> IOStateArrow s b Int
    getParamInt def n
        = getParamString n
          >>^
          (\ x -> if null x then def else read x)

    -- ------------------------------------------------------------

    -- | reset global error variable

    changeErrorStatus       :: (Int -> Int -> Int) -> IOStateArrow s Int Int
    changeErrorStatus f
        = changeSysParam (\ l s -> s { xio_errorStatus = f l (xio_errorStatus s) })

    clearErrStatus          :: IOStateArrow s b b
    clearErrStatus
        = perform (constA 0 >>> changeErrorStatus min)

    -- | set global error variable

    setErrStatus            :: IOStateArrow s Int Int
    setErrStatus
        = changeErrorStatus max

    -- | read current global error status

    getErrStatus            :: IOStateArrow s XmlTree Int
    getErrStatus
        = getSysParam xio_errorStatus

    -- | raise the global error status level to that of the input tree

    setErrMsgStatus :: IOStateArrow s XmlTree XmlTree
    setErrMsgStatus
        = perform ( getErrorLevel
                    >>>
                    setErrStatus
                  )

    -- | set the error message handler and the flag for collecting the errors

    setErrorMsgHandler      :: Bool -> (String -> IO ()) -> IOStateArrow s b b
    setErrorMsgHandler c f
        = changeSysParam cf
        where
        cf _ s = s { xio_errorMsgHandler = f
                   , xio_errorMsgCollect = c }

    -- | error message handler for output to stderr

    sysErrorMsg             :: IOStateArrow s XmlTree XmlTree
    sysErrorMsg
        = perform ( getErrorLevel &&& getErrorMsg
                    >>>
                    arr formatErrorMsg
                    >>>
                    ( IOSLA $ \ s e ->
                      do
                      (xio_errorMsgHandler . xio_sysState $ s) e
                      return (s, undefined)
                    )
                  )
        where
        formatErrorMsg (level, msg) = "\n" ++ errClass level ++ ": " ++ msg
        errClass l
            = fromMaybe "fatal error" . lookup l $ msgList
              where
              msgList       = [ (c_ok,      "no error")
                              , (c_warn,    "warning")
                              , (c_err,     "error")
                              , (c_fatal,   "fatal error")
                              ]

    -- | the default error message handler: error output to stderr

    errorMsgStderr          :: IOStateArrow s b b
    errorMsgStderr          = setErrorMsgHandler False (hPutStrLn stderr)

    -- | error message handler for collecting errors

    errorMsgCollect         :: IOStateArrow s b b
    errorMsgCollect         = setErrorMsgHandler True (const $ return ())

    -- | error message handler for output to stderr and collecting

    errorMsgStderrAndCollect        :: IOStateArrow s b b
    errorMsgStderrAndCollect        = setErrorMsgHandler True (hPutStrLn stderr)

    -- | error message handler for ignoring errors

    errorMsgIgnore          :: IOStateArrow s b b
    errorMsgIgnore          = setErrorMsgHandler False (const $ return ())

    -- |
    -- if error messages are collected by the error handler for
    -- processing these messages by the calling application,
    -- this arrow reads the stored messages and clears the error message store

    getErrorMessages        :: IOStateArrow s b XmlTree
    getErrorMessages
        = getSysParam (reverse . xio_errorMsgList)          -- reverse the list of errors
          >>>
          clearErrorMsgList                                 -- clear the error list in the system state
          >>>
          arrL id

    clearErrorMsgList       :: IOStateArrow s b b
    clearErrorMsgList
        = changeSysParam (\ _ s -> s { xio_errorMsgList = [] } )

    addToErrorMsgList       :: IOStateArrow s XmlTree XmlTree
    addToErrorMsgList
        = changeSysParam cf
        where
        cf t s = if xio_errorMsgCollect s
                 then s { xio_errorMsgList = t : xio_errorMsgList s }
                 else s

    -- ------------------------------------------------------------

    -- |
    -- filter error messages from input trees and issue errors

    filterErrorMsg          :: IOStateArrow s XmlTree XmlTree
    filterErrorMsg
        = ( setErrMsgStatus
            >>>
            sysErrorMsg
            >>>
            addToErrorMsgList
            >>>
            none
          )
          `when`
          isError

    -- | generate a warnig message

    issueWarn               :: String -> IOStateArrow s b b
    issueWarn msg           = perform (warn msg  >>> filterErrorMsg)

    -- | generate an error message
    issueErr                :: String -> IOStateArrow s b b
    issueErr msg            = perform (err msg   >>> filterErrorMsg)

    -- | generate a fatal error message, e.g. document not found

    issueFatal              :: String -> IOStateArrow s b b
    issueFatal msg          = perform (fatal msg >>> filterErrorMsg)

    -- |
    -- add the error level and the module where the error occured
    -- to the attributes of a document root node and remove the children when level is greater or equal to 'c_err'.
    -- called by 'setDocumentStatusFromSystemState' when the system state indicates an error

    setDocumentStatus       :: Int -> String -> IOStateArrow s XmlTree XmlTree
    setDocumentStatus level msg
        = ( addAttrl ( sattr a_status (show level)
                       <+>
                       sattr a_module msg
                     )
            >>>
            ( if level >= c_err
              then setChildren []
              else this
            )
          )
          `when`
          isRoot

    -- |
    -- check whether the error level attribute in the system state
    -- is set to error, in this case the children of the document root are
    -- removed and the module name where the error occured and the error level are added as attributes with 'setDocumentStatus'
    -- else nothing is changed

    setDocumentStatusFromSystemState                :: String -> IOStateArrow s XmlTree XmlTree
    setDocumentStatusFromSystemState msg
        = setStatus $< getErrStatus
        where
        setStatus level
            | level <= c_warn       = this
            | otherwise             = setDocumentStatus level msg

    -- |
    -- check whether tree is a document root and the status attribute has a value less than 'c_err'

    documentStatusOk        :: ArrowXml a => a XmlTree XmlTree
    documentStatusOk
        = isRoot
          >>>
          ( (getAttrValue a_status
             >>>
             isA (\ v -> null v || ((read v)::Int) <= c_warn)
            )
            `guards`
            this
          )

    -- ------------------------------------------------------------

    -- | set the base URI of a document, used e.g. for reading includes, e.g. external entities,
    -- the input must be an absolute URI

    setBaseURI              :: IOStateArrow s String String
    setBaseURI
        = changeSysParam (\ b s -> s { xio_baseURI = b } )
          >>>
          traceValue 2 (("setBaseURI: new base URI is " ++) . show)

    -- | read the base URI from the globale state

    getBaseURI              :: IOStateArrow s b String
    getBaseURI
        = getSysParam xio_baseURI
          >>>
          ( ( getDefaultBaseURI
              >>>
              setBaseURI
              >>>
              getBaseURI
            )
            `when`
            isA null                                -- set and get it, if not yet done
          )

    -- | change the base URI with a possibly relative URI, can be used for
    -- evaluating the xml:base attribute. Returns the new absolute base URI.
    -- Fails, if input is not parsable with parseURIReference
    --
    -- see also: 'setBaseURI', 'mkAbsURI'

    changeBaseURI           :: IOStateArrow s String String
    changeBaseURI
        = mkAbsURI
          >>>
          setBaseURI

    -- | set the default base URI, if parameter is null, the system base (@ file:\/\/\/\<cwd\>\/ @) is used,
    -- else the parameter, must be called before any document is read

    setDefaultBaseURI       :: String -> IOStateArrow s b String
    setDefaultBaseURI base
        = ( if null base
            then arrIO getDir
            else constA base
          )
          >>>
          changeSysParam (\ b s -> s { xio_defaultBaseURI = b } )
          >>>
          traceValue 2 (("setDefaultBaseURI: new default base URI is " ++) . show)
        where
        getDir _ = do
                   cwd <- getCurrentDirectory
                   return ("file://" ++ normalize cwd ++ "/")

        -- under Windows getCurrentDirectory returns something like: "c:\path\to\file"
        -- backslaches are not allowed in URIs and paths must start with a /
        -- so this is transformed into "/c:/path/to/file"

        normalize wd'@(d : ':' : _)
            | d `elem` ['A'..'Z'] || d `elem` ['a'..'z']
                = '/' : concatMap win32ToUriChar wd'
        normalize wd'
            = concatMap escapeNonUriChar wd'

        win32ToUriChar '\\' = "/"
        win32ToUriChar c    = escapeNonUriChar c

        escapeNonUriChar c  = escapeURIChar isUnescapedInURI c   -- from Network.URI

    -- | get the default base URI

    getDefaultBaseURI       :: IOStateArrow s b String
    getDefaultBaseURI
        = getSysParam xio_defaultBaseURI            -- read default uri in system  state
          >>>
          ( setDefaultBaseURI ""                    -- set the default uri in system state
            >>>
            getDefaultBaseURI ) `when` isA null     -- when uri not yet set

    -- ------------------------------------------------------------

    -- | remember base uri, run an arrow and restore the base URI, used with external entity substitution

    runInLocalURIContext    :: IOStateArrow s b c -> IOStateArrow s b c
    runInLocalURIContext f
        = ( getBaseURI &&& this )
          >>>
          ( this *** listA f )
          >>>
          ( setBaseURI *** this )
          >>>
          arrL snd

    -- ------------------------------------------------------------

    -- | set the global trace level

    setTraceLevel   :: Int -> IOStateArrow s b b
    setTraceLevel l
        = changeSysParam (\ _ s -> s { xio_trace = l } )

    -- | read the global trace level

    getTraceLevel   :: IOStateArrow s b Int
    getTraceLevel
        = getSysParam xio_trace

    -- | set the global trace command. This command does the trace output

    setTraceCmd     :: (Int -> String -> IO ()) -> IOStateArrow s b b
    setTraceCmd c
        = changeSysParam (\ _ s -> s { xio_traceCmd = c } )

    -- | acces the command for trace output

    getTraceCmd     :: IOStateArrow a b (Int -> String -> IO ())
    getTraceCmd
        = getSysParam xio_traceCmd

    -- | run an arrow with a given trace level, the old trace level is restored after the arrow execution

    withTraceLevel  :: Int -> IOStateArrow s b c -> IOStateArrow s b c
    withTraceLevel level f
        = ( getTraceLevel       &&& this )
          >>>
          ( setTraceLevel level *** listA f )
          >>>
          ( restoreTraceLevel   *** this )
          >>>
          arrL snd
        where
        restoreTraceLevel   :: IOStateArrow s Int Int
        restoreTraceLevel
            = setTraceLevel $< this

    -- | apply a trace arrow and issue message to stderr

    trace           :: Int -> IOStateArrow s b String -> IOStateArrow s b b
    trace level trc
        = perform ( trc
                    >>>
                    ( getTraceCmd &&& this )
                    >>>
                    arrIO (\ (cmd, msg) -> cmd level msg)
                  )
          `when` ( getTraceLevel
                   >>>
                   isA (>= level)
                 )

    traceOutputToStderr     :: Int -> String -> IO ()
    traceOutputToStderr _level msg
        = do
          hPutStrLn stderr msg
          hFlush stderr

    -- | trace the current value transfered in a sequence of arrows.
    --
    -- The value is formated by a string conversion function. This is a substitute for
    -- the old and less general traceString function

    traceValue              :: Int -> (b -> String) -> IOStateArrow s b b
    traceValue level trc
        = trace level (arr $ (('-' : "- (" ++ show level ++ ") ") ++) . trc)

    -- | an old alias for 'traceValue'

    traceString             :: Int -> (b -> String) -> IOStateArrow s b b
    traceString             = traceValue

    -- | issue a string message as trace

    traceMsg        :: Int -> String -> IOStateArrow s b b
    traceMsg level msg
        = traceValue level (const msg)

    -- | issue the source representation of a document if trace level >= 3
    --
    -- for better readability the source is formated with indentDoc

    traceSource     :: IOStateArrow s XmlTree XmlTree
    traceSource
        = trace 3 $
          xshow
          ( choiceA [ isRoot :-> ( indentDoc
                                   >>>
                                   getChildren
                                 )
                    , isElem :-> ( root [] [this]
                                   >>> indentDoc
                                   >>> getChildren
                                   >>> isElem
                                 )
                    , this   :-> this
                    ]
          )

    -- | issue the tree representation of a document if trace level >= 4
    traceTree       :: IOStateArrow s XmlTree XmlTree
    traceTree
        = trace 4 $
          xshow ( treeRepOfXmlDoc
                  >>>
                  addHeadlineToXmlDoc
                  >>>
                  getChildren
                )

    -- | trace a main computation step
    -- issue a message when trace level >= 1, issue document source if level >= 3, issue tree when level is >= 4

    traceDoc        :: String -> IOStateArrow s XmlTree XmlTree
    traceDoc msg
        = traceMsg 1 msg
          >>>
          traceSource
          >>>
          traceTree

    -- | trace the global state

    traceState      :: IOStateArrow s b b
    traceState
        = perform ( xshow ( (getAllParams >>. concat)
                            >>>
                            applyA (arr formatParam)
                          )
                    >>>
                    traceValue 2 ("global state:\n" ++)
                  )
          where
          -- formatParam    :: (String, XmlTrees) -> IOStateArrow s b1 XmlTree
          formatParam (n, v)
              = mkelem "param" [sattr "name" n] [arrL (const v)] <+> txt "\n"

    -- ----------------------------------------------------------

    -- | parse a URI reference, in case of a failure,
    -- try to escape unescaped chars, convert backslashes to slashes for windows paths,
    -- and try parsing again

    parseURIReference'      :: String -> Maybe URI
    parseURIReference' uri
        = parseURIReference uri
          `mplus`
          ( if unesc
            then parseURIReference uri'
            else mzero
          )
        where
        unesc       = not . all isUnescapedInURI $ uri

        escape '\\' = "/"
        escape c    = escapeURIChar isUnescapedInURI c

        uri'        = concatMap escape uri

    -- | compute the absolut URI for a given URI and a base URI

    expandURIString :: String -> String -> Maybe String
    expandURIString uri base
        = do
          base' <- parseURIReference' base
          uri'  <- parseURIReference' uri
          abs'  <- nonStrictRelativeTo uri' base'
          return $ show abs'

    -- | arrow variant of 'expandURIString', fails if 'expandURIString' returns Nothing

    expandURI               :: ArrowXml a => a (String, String) String
    expandURI
        = arrL (maybeToList . uncurry expandURIString)

    -- | arrow for expanding an input URI into an absolute URI using global base URI, fails if input is not a legal URI

    mkAbsURI                :: IOStateArrow s String String
    mkAbsURI
        = ( this &&& getBaseURI ) >>> expandURI

    -- | arrow for selecting the scheme (protocol) of the URI, fails if input is not a legal URI.
    --
    -- See Network.URI for URI components

    getSchemeFromURI        :: ArrowList a => a String String
    getSchemeFromURI        = getPartFromURI scheme
        where
        scheme = init . uriScheme

    -- | arrow for selecting the registered name (host) of the URI, fails if input is not a legal URI

    getRegNameFromURI       :: ArrowList a => a String String
    getRegNameFromURI       = getPartFromURI host
        where
        host = maybe "" uriRegName . uriAuthority

    -- | arrow for selecting the port number of the URI without leading \':\', fails if input is not a legal URI

    getPortFromURI          :: ArrowList a => a String String
    getPortFromURI          = getPartFromURI port
        where
        port = dropWhile (==':') . maybe "" uriPort . uriAuthority

    -- | arrow for selecting the user info of the URI without trailing \'\@\', fails if input is not a legal URI

    getUserInfoFromURI              :: ArrowList a => a String String
    getUserInfoFromURI              = getPartFromURI ui
        where
        ui = reverse . dropWhile (=='@') . reverse . maybe "" uriUserInfo . uriAuthority

    -- | arrow for computing the path component of an URI, fails if input is not a legal URI

    getPathFromURI          :: ArrowList a => a String String
    getPathFromURI          = getPartFromURI uriPath

    -- | arrow for computing the query component of an URI, fails if input is not a legal URI

    getQueryFromURI         :: ArrowList a => a String String
    getQueryFromURI         = getPartFromURI uriQuery

    -- | arrow for computing the fragment component of an URI, fails if input is not a legal URI

    getFragmentFromURI      :: ArrowList a => a String String
    getFragmentFromURI      = getPartFromURI uriFragment

    -- | arrow for computing the path component of an URI, fails if input is not a legal URI

    getPartFromURI          :: ArrowList a => (URI -> String) -> a String String
    getPartFromURI sel
        = arrL (maybeToList . getPart)
          where
          getPart s = do
                      uri <- parseURIReference' s
                      return (sel uri)

    -- ------------------------------------------------------------

    -- | set the table mapping of file extensions to mime types in the system state
    --
    -- Default table is defined in 'Text.XML.HXT.DOM.MimeTypeDefaults'.
    -- This table is used when reading loacl files, (file: protocol) to determine the mime type

    setMimeTypeTable        :: MimeTypeTable -> IOStateArrow s b b
    setMimeTypeTable mtt
        = changeSysParam (\ _ s -> s {xio_mimeTypes = mtt})

    -- | set the table mapping of file extensions to mime types by an external config file
    --
    -- The config file must follow the conventions of /etc/mime.types on a debian linux system,
    -- that means all empty lines and all lines starting with a # are ignored. The other lines
    -- must consist of a mime type followed by a possible empty list of extensions.
    -- The list of extenstions and mime types overwrites the default list in the system state
    -- of the IOStateArrow

    setMimeTypeTableFromFile        :: FilePath -> IOStateArrow s b b
    setMimeTypeTableFromFile file
        = setMimeTypeTable $< arrIO0 ( readMimeTypeTable file)

    -- ------------------------------------------------------------