module Text.Blaze.Truncate(truncateHtml) where
import Text.Blaze(Markup)
import Text.Blaze.Internal(MarkupM(..),ChoiceString(..),StaticString(..))
import qualified Data.Text as T
import qualified Data.ByteString as B
import GHC.Exts (IsString (..))
import Prelude
import qualified Text.HTML.Truncate as HTML
data Tagged a = Tagged Int a
instance Functor Tagged where
fmap f (Tagged n a) = Tagged n (f a)
-- | Truncate the given HTML to a certain length, preserving tags. Returns the truncated Html or `Nothing` if no truncation occured.
-- Words are preserved, so if the truncated text ends within some word, that whole word is cut.
truncateHtml :: Int -- ^ The amount of characters (not counting tags) which the truncated text should have at most
-> Markup -- ^ The HTML to truncate
-> Maybe Markup -- ^ `Just` the truncated HTML or `Nothing` if no truncation occured
truncateHtml n html = case go n html of Tagged n' html' -> if n' <= 0 then filterEmptyTags html' else Nothing
where
go :: Int -> MarkupM b -> Tagged (MarkupM b)
go i (Parent t open close content) = fmap (Parent t open close) (go i content)
go i (Leaf t begin end) = Tagged i (Leaf t begin end)
go i (AddAttribute t key value h) = fmap (AddAttribute t key value) (go i h)
go i (AddCustomAttribute key value h) = fmap (AddCustomAttribute key value) (go i h)
go i (Append h1 h2) = case go i h1 of
Tagged j h1' | j <= 0 -> Tagged j (Append h1' Empty) -- FIXME: we actually want to return just Tagged j h1', but can't due to a type error
Tagged j h1' -> fmap (Append h1') (go j h2)
go i Empty = Tagged i Empty
go i (Content content) = fmap Content (truncateChoiceString i content)
-- filter _trailing_ empty tags
filterEmptyTags :: MarkupM a -> Maybe (MarkupM b)
filterEmptyTags (Parent t open close content) = fmap (Parent t open close) (filterEmptyTags content)
filterEmptyTags (Leaf _ _ _) = Nothing
filterEmptyTags (AddAttribute t key value h) = fmap (AddAttribute t key value) (filterEmptyTags h)
filterEmptyTags (AddCustomAttribute key value h) = fmap (AddCustomAttribute key value) (filterEmptyTags h)
filterEmptyTags (Append h1 h2) = case filterEmptyTags h2 of
Nothing -> filterEmptyTags h1
Just h2' -> Just (Append h1 h2')
filterEmptyTags Empty = Nothing
filterEmptyTags (Content content) = if (length' content) == 0 then Nothing else Just (Content content)
length' :: ChoiceString -> Int
length' (Static str) = length ((getString str) "")
length' (String str) = length str
length' (Text str) = T.length str
length' (ByteString str) = B.length str
length' (PreEscaped str) = length' str
length' (External _) = 0 -- note: these should not be truncated, so the behavior is a bit special here
length' (AppendChoiceString str1 str2) = length' str1 + length' str2
length' EmptyChoiceString = 0
truncateChoiceString :: Int -> ChoiceString -> Tagged ChoiceString
truncateChoiceString i _ | i <= 0 = Tagged 0 EmptyChoiceString
truncateChoiceString i (Static str) = case HTML.truncateStringLike i ((getString str) "") of
(i',str') -> Tagged (max 0 i') $ Static (fromString str')
truncateChoiceString i (String str) = case HTML.truncateStringLike i str of
(i',str') -> Tagged (max 0 i') $ String str'
truncateChoiceString i (Text str) = case HTML.truncateStringLike i str of
(i',str') -> Tagged (max 0 i') $ Text str'
truncateChoiceString i (ByteString str) = case HTML.truncateStringLike i str of
(i',str') -> Tagged (max 0 i') $ ByteString str'
-- truncateChoiceString i (ByteString str) = case B.take i str of
-- str' -> Tagged (max 0 (i - B.length str')) $ ByteString str'
truncateChoiceString i (PreEscaped str) = case truncateChoiceStringPreEscaped i str of
Tagged i' str' -> Tagged (max 0 i') $ PreEscaped str'
truncateChoiceString i str@(External _) = Tagged i str
truncateChoiceString i (AppendChoiceString str1 str2) =
case truncateChoiceString i str1 of
Tagged i' str1' -> case truncateChoiceString i' str2 of
Tagged i'' str2' -> Tagged (max 0 i'') $ AppendChoiceString str1' str2'
truncateChoiceString i EmptyChoiceString = Tagged i EmptyChoiceString
truncateChoiceStringPreEscaped :: Int -> ChoiceString -> Tagged ChoiceString
truncateChoiceStringPreEscaped i _ | i <= 0 = Tagged 0 EmptyChoiceString
truncateChoiceStringPreEscaped i (Static str) = case HTML.truncateHtml' i ((getString str) "") of
(i',str') -> Tagged (max 0 i') $ Static (fromString str')
truncateChoiceStringPreEscaped i (String str) = case HTML.truncateHtml' i str of
(i',str') -> Tagged (max 0 i') $ String str'
truncateChoiceStringPreEscaped i (Text str) = case HTML.truncateHtml' i str of
(i',str') -> Tagged (max 0 i') $ Text str'
truncateChoiceStringPreEscaped i (ByteString str) = case HTML.truncateHtml' i str of
(i',str') -> Tagged (max 0 i') $ ByteString str'
-- truncateChoiceStringPreEscaped i (ByteString str) = case B.take i str of
-- str' -> Tagged (max 0 (i - B.length str')) $ ByteString str'
truncateChoiceStringPreEscaped i (PreEscaped str) = case truncateChoiceStringPreEscaped i str of
Tagged i' str' -> Tagged (max 0 i') $ PreEscaped str'
truncateChoiceStringPreEscaped i str@(External _) = Tagged i str
truncateChoiceStringPreEscaped i (AppendChoiceString str1 str2) =
case truncateChoiceStringPreEscaped i str1 of
Tagged i' str1' -> case truncateChoiceStringPreEscaped i' str2 of
Tagged i'' str2' -> Tagged (max 0 i'') $ AppendChoiceString str1' str2'
truncateChoiceStringPreEscaped i EmptyChoiceString = Tagged i EmptyChoiceString