{-# LANGUAGE OverloadedStrings, GeneralizedNewtypeDeriving, Rank2Types,
FlexibleInstances, ExistentialQuantification,
DeriveDataTypeable #-}
module Text.Blaze.Internal
(
ChoiceString (..)
, StaticString (..)
, MarkupM (..)
, Markup
, Tag
, Attribute
, AttributeValue
, customParent
, customLeaf
, attribute
, dataAttribute
, customAttribute
, text
, preEscapedText
, lazyText
, preEscapedLazyText
, string
, preEscapedString
, unsafeByteString
, unsafeLazyByteString
, textTag
, stringTag
, textValue
, preEscapedTextValue
, lazyTextValue
, preEscapedLazyTextValue
, stringValue
, preEscapedStringValue
, unsafeByteStringValue
, unsafeLazyByteStringValue
, Attributable
, (!)
, (!?)
, contents
, external
, null
) where
import Prelude hiding (null)
import Data.Monoid (Monoid, mappend, mempty, mconcat)
import Unsafe.Coerce (unsafeCoerce)
import qualified Data.List as List
import Data.ByteString.Char8 (ByteString)
import Data.Text (Text)
import Data.Typeable (Typeable)
import GHC.Exts (IsString (..))
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as BL
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import qualified Data.Text.Lazy as LT
data StaticString = StaticString
{ getString :: String -> String
, getUtf8ByteString :: B.ByteString
, getText :: Text
}
instance IsString StaticString where
fromString s = let t = T.pack s
in StaticString (s ++) (T.encodeUtf8 t) t
data ChoiceString
= Static {-# UNPACK #-} !StaticString
| String String
| Text Text
| ByteString B.ByteString
| PreEscaped ChoiceString
| External ChoiceString
| AppendChoiceString ChoiceString ChoiceString
| EmptyChoiceString
instance Monoid ChoiceString where
mempty = EmptyChoiceString
{-# INLINE mempty #-}
mappend = AppendChoiceString
{-# INLINE mappend #-}
instance IsString ChoiceString where
fromString = String
{-# INLINE fromString #-}
data MarkupM a
= forall b. Parent StaticString StaticString StaticString (MarkupM b)
| forall b. CustomParent ChoiceString (MarkupM b)
| Leaf StaticString StaticString StaticString
| CustomLeaf ChoiceString Bool
| Content ChoiceString
| forall b c. Append (MarkupM b) (MarkupM c)
| AddAttribute StaticString StaticString ChoiceString (MarkupM a)
| AddCustomAttribute ChoiceString ChoiceString (MarkupM a)
| Empty
deriving (Typeable)
type Markup = MarkupM ()
instance Monoid a => Monoid (MarkupM a) where
mempty = Empty
{-# INLINE mempty #-}
mappend x y = Append x y
{-# INLINE mappend #-}
mconcat = foldr Append Empty
{-# INLINE mconcat #-}
instance Functor MarkupM where
fmap _ = unsafeCoerce
instance Monad MarkupM where
return _ = Empty
{-# INLINE return #-}
(>>) = Append
{-# INLINE (>>) #-}
h1 >>= f = h1 >> f
(error "Text.Blaze.Internal.MarkupM: invalid use of monadic bind")
{-# INLINE (>>=) #-}
instance IsString (MarkupM a) where
fromString = Content . fromString
{-# INLINE fromString #-}
newtype Tag = Tag { unTag :: StaticString }
deriving (IsString)
newtype Attribute = Attribute (forall a. MarkupM a -> MarkupM a)
instance Monoid Attribute where
mempty = Attribute id
Attribute f `mappend` Attribute g = Attribute (g . f)
newtype AttributeValue = AttributeValue { unAttributeValue :: ChoiceString }
deriving (IsString, Monoid)
customParent :: Tag
-> Markup
-> Markup
customParent tag = CustomParent (Static $ unTag tag)
customLeaf :: Tag
-> Bool
-> Markup
customLeaf tag = CustomLeaf (Static $ unTag tag)
attribute :: Tag
-> Tag
-> AttributeValue
-> Attribute
attribute rawKey key value = Attribute $
AddAttribute (unTag rawKey) (unTag key) (unAttributeValue value)
{-# INLINE attribute #-}
dataAttribute :: Tag
-> AttributeValue
-> Attribute
dataAttribute tag value = Attribute $ AddCustomAttribute
(Static "data-" `mappend` Static (unTag tag))
(unAttributeValue value)
{-# INLINE dataAttribute #-}
customAttribute :: Tag
-> AttributeValue
-> Attribute
customAttribute tag value = Attribute $ AddCustomAttribute
(Static $ unTag tag)
(unAttributeValue value)
{-# INLINE customAttribute #-}
text :: Text
-> Markup
text = Content . Text
{-# INLINE text #-}
preEscapedText :: Text
-> Markup
preEscapedText = Content . PreEscaped . Text
{-# INLINE preEscapedText #-}
lazyText :: LT.Text
-> Markup
lazyText = mconcat . map text . LT.toChunks
{-# INLINE lazyText #-}
preEscapedLazyText :: LT.Text
-> Markup
preEscapedLazyText = mconcat . map preEscapedText . LT.toChunks
string :: String
-> Markup
string = Content . String
{-# INLINE string #-}
preEscapedString :: String
-> Markup
preEscapedString = Content . PreEscaped . String
{-# INLINE preEscapedString #-}
unsafeByteString :: ByteString
-> Markup
unsafeByteString = Content . ByteString
{-# INLINE unsafeByteString #-}
unsafeLazyByteString :: BL.ByteString
-> Markup
unsafeLazyByteString = mconcat . map unsafeByteString . BL.toChunks
{-# INLINE unsafeLazyByteString #-}
textTag :: Text
-> Tag
textTag t = Tag $ StaticString (T.unpack t ++) (T.encodeUtf8 t) t
stringTag :: String
-> Tag
stringTag = Tag . fromString
textValue :: Text
-> AttributeValue
textValue = AttributeValue . Text
{-# INLINE textValue #-}
preEscapedTextValue :: Text
-> AttributeValue
preEscapedTextValue = AttributeValue . PreEscaped . Text
{-# INLINE preEscapedTextValue #-}
lazyTextValue :: LT.Text
-> AttributeValue
lazyTextValue = mconcat . map textValue . LT.toChunks
{-# INLINE lazyTextValue #-}
preEscapedLazyTextValue :: LT.Text
-> AttributeValue
preEscapedLazyTextValue = mconcat . map preEscapedTextValue . LT.toChunks
{-# INLINE preEscapedLazyTextValue #-}
stringValue :: String -> AttributeValue
stringValue = AttributeValue . String
{-# INLINE stringValue #-}
preEscapedStringValue :: String -> AttributeValue
preEscapedStringValue = AttributeValue . PreEscaped . String
{-# INLINE preEscapedStringValue #-}
unsafeByteStringValue :: ByteString
-> AttributeValue
unsafeByteStringValue = AttributeValue . ByteString
{-# INLINE unsafeByteStringValue #-}
unsafeLazyByteStringValue :: BL.ByteString
-> AttributeValue
unsafeLazyByteStringValue = mconcat . map unsafeByteStringValue . BL.toChunks
{-# INLINE unsafeLazyByteStringValue #-}
class Attributable h where
(!) :: h -> Attribute -> h
instance Attributable (MarkupM a) where
h ! (Attribute f) = f h
{-# INLINE (!) #-}
instance Attributable (MarkupM a -> MarkupM b) where
h ! f = (! f) . h
{-# INLINE (!) #-}
(!?) :: Attributable h => h -> (Bool, Attribute) -> h
(!?) h (c, a) = if c then h ! a else h
external :: MarkupM a -> MarkupM a
external (Content x) = Content $ External x
external (Append x y) = Append (external x) (external y)
external (Parent x y z i) = Parent x y z $ external i
external (CustomParent x i) = CustomParent x $ external i
external (AddAttribute x y z i) = AddAttribute x y z $ external i
external (AddCustomAttribute x y i) = AddCustomAttribute x y $ external i
external x = x
{-# INLINE external #-}
contents :: MarkupM a -> MarkupM b
contents (Parent _ _ _ c) = contents c
contents (CustomParent _ c) = contents c
contents (Content c) = Content c
contents (Append c1 c2) = Append (contents c1) (contents c2)
contents (AddAttribute _ _ _ c) = contents c
contents (AddCustomAttribute _ _ c) = contents c
contents _ = Empty
null :: MarkupM a -> Bool
null markup = case markup of
Parent _ _ _ _ -> False
CustomParent _ _ -> False
Leaf _ _ _ -> False
CustomLeaf _ _ -> False
Content c -> emptyChoiceString c
Append c1 c2 -> null c1 && null c2
AddAttribute _ _ _ c -> null c
AddCustomAttribute _ _ c -> null c
Empty -> True
where
emptyChoiceString cs = case cs of
Static ss -> emptyStaticString ss
String s -> List.null s
Text t -> T.null t
ByteString bs -> B.null bs
PreEscaped c -> emptyChoiceString c
External c -> emptyChoiceString c
AppendChoiceString c1 c2 -> emptyChoiceString c1 && emptyChoiceString c2
EmptyChoiceString -> True
emptyStaticString = B.null . getUtf8ByteString