1 {-# LANGUAGE OverloadedStrings #-}
2 {-# LANGUAGE FlexibleInstances #-}
3 {-# OPTIONS_GHC -fno-warn-orphans #-}
4 module Text.Blaze.Utils where
6 import Blaze.ByteString.Builder (Builder)
7 import Control.Applicative (Applicative(..))
8 import Control.Monad (Monad(..))
10 import Data.Char (Char)
11 import Data.Eq (Eq(..))
12 import Data.Foldable (Foldable(..))
13 import Data.Function ((.), ($))
14 import Data.Functor ((<$>))
16 import Data.Maybe (Maybe(..), maybe)
17 import Data.Monoid (Monoid(..))
18 import Data.Semigroup (Semigroup(..))
19 import Data.String (IsString(..))
20 import Data.Text (Text)
21 import Prelude (Num(..))
23 import Text.Blaze as B
24 import Text.Blaze.Internal as B hiding (null)
25 import Text.Show (Show(..))
26 import qualified Blaze.ByteString.Builder as BS
27 import qualified Blaze.ByteString.Builder.Html.Utf8 as BS
28 import qualified Data.ByteString as BS
29 import qualified Data.ByteString.Lazy as BSL
30 import qualified Data.List as List
31 import qualified Data.Text as Text
32 import qualified Data.Text.Encoding as BS
33 import qualified Text.Blaze.Html5 as H
34 import qualified Text.Blaze.Renderer.Utf8 as BS
36 -- | 'Attribute' in 'Maybe'.
38 (!??) :: Attributable h => h -> Maybe Attribute -> h
39 (!??) h = maybe h (h !)
41 whenMarkup :: MarkupM a -> MarkupM () -> MarkupM ()
42 whenMarkup Empty{} _b = return ()
45 whenJust :: Applicative m => Maybe a -> (a -> m ()) -> m ()
46 whenJust Nothing _f = pure ()
47 whenJust (Just a) f = f a
49 whenSome :: (Applicative m, Foldable f) => f a -> (f a -> m ()) -> m ()
50 whenSome x _f | null x = pure ()
53 whenText :: Applicative m => Text -> (Text -> m ()) -> m ()
54 whenText "" _f = pure ()
57 instance Semigroup H.AttributeValue where
60 -- * Class 'AttrValue'
61 class AttrValue a where
62 attrValue :: a -> H.AttributeValue
63 instance AttrValue Text where
64 attrValue = fromString . Text.unpack
65 instance AttrValue Int where
66 attrValue = fromString . show
67 instance AttrValue [Char] where
68 attrValue = fromString
72 mayAttr :: (AttributeValue -> Attribute) -> a -> Maybe Attribute
73 instance MayAttr a => MayAttr (Maybe a) where
74 mayAttr a t = t >>= mayAttr a
75 instance MayAttr Text where
76 mayAttr _ "" = Nothing
77 mayAttr a t = Just (a $ fromString $ Text.unpack t)
78 instance MayAttr Int where
79 mayAttr a t = Just (a $ fromString $ show t)
80 instance MayAttr [Char] where
81 mayAttr _ "" = Nothing
82 mayAttr a t = Just (a $ fromString t)
91 -- | Render some 'Markup' to a 'Builder'.
93 -- An 'IndentTag' is queried on each tag
94 -- to indent tags differently according to their names.
95 prettyMarkupBuilder :: (Text -> IndentTag) -> Markup -> Builder
96 prettyMarkupBuilder indentTag = go IndentTagPreserve "\n" mempty
100 bs_Attrs i ind t_tag attrs =
101 case {-List.reverse-} attrs of
105 let ind_key = BS.fromText $ Text.replicate (Text.length t_tag + 1) " " in
108 IndentTagChildren -> ind<>ind_key
109 IndentTagPreserve -> mempty
110 IndentTagText -> mempty in
111 a0 <> foldMap (ind_attr <>) as
112 go :: IndentTag -> Builder -> [Builder] -> MarkupM b -> Builder
113 go i ind attrs (Parent tag open close content) =
114 let i' = indentTag (getText tag) in
115 (if i==IndentTagChildren then ind else mempty)
116 <> BS.copyByteString (getUtf8ByteString open)
117 <> bs_Attrs i ind (getText tag) attrs
119 <> (if i==IndentTagChildren && i'==IndentTagText then ind<>inc else mempty)
120 <> go i' (if i'/=IndentTagPreserve then ind<>inc else ind) mempty content
121 <> (if (i==IndentTagChildren && i'==IndentTagText) || i'==IndentTagChildren then ind else mempty)
122 <> BS.copyByteString (getUtf8ByteString close)
123 go i ind attrs (CustomParent tag content) =
124 let i' = indentTag (t_ChoiceString tag) in
125 let t_tag = t_ChoiceString tag in
126 (if i==IndentTagChildren then ind else mempty)
129 <> bs_Attrs i ind t_tag attrs
131 <> (if i==IndentTagChildren && i'==IndentTagText then ind<>inc else mempty)
132 <> go i' (if i'/=IndentTagPreserve then ind<>inc else ind) mempty content
133 <> (if (i==IndentTagChildren && i'==IndentTagText) || i'==IndentTagChildren then ind else mempty)
134 <> BS.fromByteString "</"
135 <> bs_ChoiceString tag
137 go i ind attrs (Leaf tag begin end _) =
138 (if i==IndentTagChildren then ind else mempty)
139 <> BS.copyByteString (getUtf8ByteString begin)
140 <> bs_Attrs i ind (getText tag) attrs
141 <> BS.copyByteString (getUtf8ByteString end)
142 go i ind attrs (CustomLeaf tag close _) =
143 let t_tag = t_ChoiceString tag in
144 (if i==IndentTagChildren then ind else mempty)
147 <> bs_Attrs i ind t_tag attrs
148 <> (if close then BS.fromByteString "/>" else BS.fromChar '>')
149 go i ind attrs (AddAttribute _ key value m) =
151 ( BS.copyByteString (getUtf8ByteString key)
152 <> bs_ChoiceString value
155 go i ind attrs (AddCustomAttribute key value m) =
158 <> bs_ChoiceString key
159 <> BS.fromByteString "=\""
160 <> bs_ChoiceString value
163 go i ind _attrs (Content content _) =
164 if i/=IndentTagPreserve
165 then indentChoiceString ind content
166 else bs_ChoiceString content
167 go i ind _attrs (Comment comment _) =
168 (if i==IndentTagChildren then ind else mempty)
169 <> BS.fromByteString "<!--"
170 <> (if i==IndentTagChildren
171 then indentChoiceString ind
174 <> BS.fromByteString "-->"
175 go i ind attrs (Append m1 m2) =
178 go _i _ind _attrs (Empty _) = mempty
181 -- | Render 'Markup' to a lazy UTF-8 encoded 'BSL.ByteString'.
182 prettyMarkup :: (Text -> IndentTag) -> Markup -> BSL.ByteString
183 prettyMarkup ind = BS.toLazyByteString . prettyMarkupBuilder ind
185 prettyMarkupIO :: (Text -> IndentTag) -> (BS.ByteString -> IO ()) -> Markup -> IO ()
186 prettyMarkupIO ind io = BS.toByteStringIO io . prettyMarkupBuilder ind
188 bs_ChoiceString :: ChoiceString -> Builder
189 bs_ChoiceString cs = BS.renderMarkupBuilder (Content cs ())
191 t_ChoiceString :: ChoiceString -> Text
192 t_ChoiceString = BS.decodeUtf8 . BS.toByteString . bs_ChoiceString
194 -- | @indentText ind txt@ indent 'tct' with 'ind' at newlines.
195 indentText :: Builder -> Text -> Builder
198 List.intersperse ind .
199 (BS.fromHtmlEscapedText <$>) .
202 -- | Render an indented 'ChoiceString'.
203 indentChoiceString :: Builder -> ChoiceString -> Builder
204 indentChoiceString ind (Static s) = indentText ind $ getText s
205 indentChoiceString ind (String s) = indentText ind $ Text.pack s
206 indentChoiceString ind (Text s) = indentText ind s
207 indentChoiceString ind (ByteString s) = indentText ind $ BS.decodeUtf8 s
208 indentChoiceString ind (PreEscaped x) = case x of
209 String s -> indentText ind $ Text.pack s
210 Text s -> indentText ind s
211 s -> indentChoiceString ind s
212 indentChoiceString ind (External x) = case x of
213 -- Check that the sequence "</" is *not* in the external data.
214 String s -> if "</" `List.isInfixOf` s then mempty else BS.fromString s
215 Text s -> if "</" `Text.isInfixOf` s then mempty else BS.fromText s
216 ByteString s -> if "</" `BS.isInfixOf` s then mempty else BS.fromByteString s
217 s -> indentChoiceString ind s
218 indentChoiceString ind (AppendChoiceString x y) =
219 indentChoiceString ind x <>
220 indentChoiceString ind y
221 indentChoiceString ind EmptyChoiceString = indentText ind mempty
222 {-# INLINE indentChoiceString #-}