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

    {- |
       Module     : Text.XML.HXT.DOM.Util
       Copyright  : Copyright (C) 2008 Uwe Schmidt
       License    : MIT

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

       Little useful things for strings, lists and other values

    -}

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

    module Text.XML.HXT.DOM.Util
        ( stringTrim
        , stringToLower
        , stringToUpper
        , stringAll
        , stringFirst
        , stringLast

        , normalizeNumber
        , normalizeWhitespace
        , normalizeBlanks

        , escapeURI
        , textEscapeXml
        , stringEscapeXml
        , attrEscapeXml

        , stringToInt
        , stringToHexString
        , charToHexString
        , intToHexString
        , hexStringToInt
        , decimalStringToInt

        , doubles
        , singles
        , noDoubles

        , swap
        , partitionEither
        , toMaybe

        , uncurry3
        , uncurry4
        )
    where

    import           Data.Char
    import           Data.List
    import           Data.Maybe

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

    -- |
    -- remove leading and trailing whitespace with standard Haskell predicate isSpace

    stringTrim              :: String -> String
    stringTrim              = reverse . dropWhile isSpace . reverse . dropWhile isSpace

    -- |
    -- convert string to uppercase with standard Haskell toUpper function

    stringToUpper           :: String -> String
    stringToUpper           = map toUpper

    -- |
    -- convert string to lowercase with standard Haskell toLower function

    stringToLower           :: String -> String
    stringToLower           = map toLower

    -- | find all positions where a string occurs within another string

    stringAll       :: (Eq a) => [a] -> [a] -> [Int]
    stringAll x     = map fst . filter ((x `isPrefixOf`) . snd) . zip [0..] . tails

    -- | find the position of the first occurence of a string

    stringFirst     :: (Eq a) => [a] -> [a] -> Maybe Int
    stringFirst x   = listToMaybe . stringAll x

    -- | find the position of the last occurence of a string

    stringLast      :: (Eq a) => [a] -> [a] -> Maybe Int
    stringLast x    = listToMaybe . reverse . stringAll x

    -- ------------------------------------------------------------
    -- | Removes leading \/ trailing whitespaces and leading zeros

    normalizeNumber         :: String -> String
    normalizeNumber
        = reverse . dropWhile (== ' ') . reverse .
          dropWhile (\x -> x == '0' || x == ' ')

    -- | Reduce whitespace sequences to a single whitespace

    normalizeWhitespace     :: String -> String
    normalizeWhitespace     = unwords . words

    -- | replace all whitespace chars by blanks

    normalizeBlanks         :: String -> String
    normalizeBlanks         = map (\ x -> if isSpace x then ' ' else x)

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

    -- | Escape all disallowed characters in URI
    -- references (see <http://www.w3.org/TR/xlink/#link-locators>)

    escapeURI :: String -> String
    escapeURI ref
        = concatMap replace ref
          where
          notAllowed        :: Char -> Bool
          notAllowed c
              = c < '\31'
                ||
                c `elem` ['\DEL', ' ', '<', '>', '\"', '{', '}', '|', '\\', '^', '`' ]

          replace :: Char -> String
          replace c
              | notAllowed c
                  = '%' : charToHexString c
              | otherwise
                  = [c]

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

    escapeXml               :: String -> String -> String
    escapeXml escSet
        = concatMap esc
          where
          esc c
              | c `elem` escSet
                  = "&#" ++ show (fromEnum c) ++ ";"
              | otherwise
                  = [c]

    -- |
    -- escape XML chars &lt;, &gt;, &quot;,  and ampercent by transforming them into character references
    --
    -- see also : 'attrEscapeXml'

    stringEscapeXml :: String -> String
    stringEscapeXml = escapeXml "<>\"\'&"

    -- |
    -- escape XML chars &lt;  and ampercent by transforming them into character references, used for escaping text nodes
    --
    -- see also : 'attrEscapeXml'

    textEscapeXml           :: String -> String
    textEscapeXml           = escapeXml "<&"

    -- |
    -- escape XML chars in attribute values, same as stringEscapeXml, but none blank whitespace
    -- is also escaped
    --
    -- see also : 'stringEscapeXml'

    attrEscapeXml           :: String -> String
    attrEscapeXml           = escapeXml "<>\"\'&\n\r\t"

    stringToInt             :: Int -> String -> Int
    stringToInt base digits
        = sign * (foldl acc 0 $ concatMap digToInt digits1)
          where
          splitSign ('-' : ds) = ((-1), ds)
          splitSign ('+' : ds) = ( 1  , ds)
          splitSign ds         = ( 1  , ds)
          (sign, digits1)      = splitSign digits
          digToInt c
              | c >= '0' && c <= '9'
                  = [ord c - ord '0']
              | c >= 'A' && c <= 'Z'
                  =  [ord c - ord 'A' + 10]
              | c >= 'a' && c <= 'z'
                  =  [ord c - ord 'a' + 10]
              | otherwise
                  = []
          acc i1 i0
              = i1 * base + i0

    -- |
    -- convert a string of hexadecimal digits into an Int

    hexStringToInt          :: String -> Int
    hexStringToInt          = stringToInt 16

    -- |
    -- convert a string of digits into an Int

    decimalStringToInt      :: String -> Int
    decimalStringToInt      = stringToInt 10

    -- |
    -- convert a string into a hexadecimal string applying charToHexString
    --
    -- see also : 'charToHexString'

    stringToHexString       :: String -> String
    stringToHexString       = concatMap charToHexString

    -- |
    -- convert a char (byte) into a 2-digit hexadecimal string
    --
    -- see also : 'stringToHexString', 'intToHexString'

    charToHexString         :: Char -> String
    charToHexString c
        = [ fourBitsToChar (c' `div` 16)
          , fourBitsToChar (c' `mod` 16)
          ]
        where
        c' = fromEnum c

    -- |
    -- convert a none negative Int into a hexadecimal string
    --
    -- see also : 'charToHexString'

    intToHexString          :: Int -> String
    intToHexString i
        | i == 0
            = "0"
        | i > 0
            = intToStr i
        | otherwise
            = error ("intToHexString: negative argument " ++ show i)
        where
        intToStr 0  = ""
        intToStr i' = intToStr (i' `div` 16) ++ [fourBitsToChar (i' `mod` 16)]

    fourBitsToChar          :: Int -> Char
    fourBitsToChar i        = "0123456789ABCDEF" !! i

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

    -- |
    -- take all elements of a list which occur more than once. The result does not contain doubles.
    -- (doubles . doubles == doubles)

    doubles :: Eq a => [a] -> [a]
    doubles
        = doubles' []
          where
          doubles' acc []
              = acc
          doubles' acc (e : s)
              | e `elem` s
                &&
                e `notElem` acc
                  = doubles' (e:acc) s
              | otherwise
                  = doubles' acc s

    -- |
    -- drop all elements from a list which occur more than once.

    singles :: Eq a => [a] -> [a]
    singles
        = singles' []
          where
          singles' acc []
              = acc
          singles' acc (e : s)
              | e `elem` s
                ||
                e `elem` acc
                  = singles' acc s
              | otherwise
                  = singles' (e : acc) s

    -- |
    -- remove duplicates from list

    noDoubles :: Eq a => [a] -> [a]
    noDoubles []
        = []
    noDoubles (e : s)
        | e `elem` s = noDoubles s
        | otherwise  = e : noDoubles s

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

    swap :: (a,b) -> (b,a)
    swap (x,y) = (y,x)

    partitionEither :: [Either a b] -> ([a], [b])
    partitionEither =
       foldr (\x ~(ls,rs) -> either (\l -> (l:ls,rs)) (\r -> (ls,r:rs)) x) ([],[])

    toMaybe :: Bool -> a -> Maybe a
    toMaybe False _ = Nothing
    toMaybe True  x = Just x

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

    -- | mothers little helpers for to much curry

    uncurry3                        :: (a -> b -> c -> d) -> (a, b, c) -> d
    uncurry3 f ~(a, b, c)           = f a b c

    uncurry4                        :: (a -> b -> c -> d -> e) -> (a, b, c, d) -> e
    uncurry4 f ~(a, b, c, d)        = f a b c d

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