1 {-# LANGUAGE OverloadedStrings #-}
2 {-# OPTIONS_GHC -fno-warn-orphans #-}
3 module Text.Blaze.Utils where
5 import Blaze.ByteString.Builder (Builder)
6 import Control.Monad (return)
8 import Data.Eq (Eq(..))
9 import Data.Function ((.), ($))
10 import Data.Functor ((<$>))
12 import Data.Maybe (Maybe(..), maybe)
13 import Data.Monoid (Monoid(..))
14 import Data.Semigroup (Semigroup(..))
15 import Data.String (IsString(..))
16 import Data.Text (Text)
18 import Text.Blaze as B
19 import Text.Blaze.Internal as B
20 import Text.Show (Show(..))
21 import qualified Data.List as List
22 import qualified Blaze.ByteString.Builder as BS
23 import qualified Blaze.ByteString.Builder.Html.Utf8 as BS
24 import qualified Data.ByteString as BS
25 import qualified Data.ByteString.Lazy as BSL
26 import qualified Data.Text as Text
27 import qualified Text.Blaze.Html5 as H
28 import qualified Text.Blaze.Renderer.Utf8 as BS
29 import qualified Data.Text.Encoding as BS
31 -- | 'Attribute' in 'Maybe'.
33 (!??) :: Attributable h => h -> (Maybe a, a -> Attribute) -> h
34 (!??) h (m,a) = maybe h (\x -> h ! a x) m
36 whenMarkup :: MarkupM a -> MarkupM () -> MarkupM ()
37 whenMarkup Empty{} _b = return ()
40 instance Semigroup H.AttributeValue where
43 -- * Class 'Attributable'
44 class AttrValue a where
45 attrValue :: a -> H.AttributeValue
46 instance AttrValue Text where
47 attrValue = fromString . Text.unpack
48 instance AttrValue Int where
49 attrValue = fromString . show
58 -- | Render some 'Markup' to a 'Builder'.
60 -- An 'IndentTag' is queried on each tag
61 -- to indent tags differently according to their names.
62 prettyMarkupBuilder :: (Text -> IndentTag) -> Markup -> Builder
63 prettyMarkupBuilder indentTag = go IndentTagPreserve "\n" mempty
67 go :: IndentTag -> Builder -> Builder -> MarkupM b -> Builder
68 go i ind attrs (Parent tag open close content) =
69 let i' = indentTag (getText tag) in
70 (if i==IndentTagChildren then ind else mempty)
71 <> BS.copyByteString (getUtf8ByteString open)
74 <> (if i==IndentTagChildren && i'==IndentTagText then ind<>inc else mempty)
75 <> go i' (if i'/=IndentTagPreserve then ind<>inc else ind) mempty content
76 <> (if (i==IndentTagChildren && i'==IndentTagText) || i'==IndentTagChildren then ind else mempty)
77 <> BS.copyByteString (getUtf8ByteString close)
78 go i ind attrs (CustomParent tag content) =
79 let i' = indentTag (t_ChoiceString tag) in
80 (if i==IndentTagChildren then ind else mempty)
82 <> bs_ChoiceString tag
85 <> (if i==IndentTagChildren && i'==IndentTagText then ind<>inc else mempty)
86 <> go i' (if i'/=IndentTagPreserve then ind<>inc else ind) mempty content
87 <> (if (i==IndentTagChildren && i'==IndentTagText) || i'==IndentTagChildren then ind else mempty)
88 <> BS.fromByteString "</"
89 <> bs_ChoiceString tag
91 go i ind attrs (Leaf _tag begin end _) =
92 (if i==IndentTagChildren then ind else mempty)
93 <> BS.copyByteString (getUtf8ByteString begin)
95 <> BS.copyByteString (getUtf8ByteString end)
96 go i ind attrs (CustomLeaf tag close _) =
97 (if i==IndentTagChildren then ind else mempty)
99 <> bs_ChoiceString tag
101 <> (if close then BS.fromByteString "/>" else BS.fromChar '>')
102 go i ind attrs (AddAttribute _ key value m) =
103 go i ind (BS.copyByteString (getUtf8ByteString key)
104 <> bs_ChoiceString value
107 go i ind attrs (AddCustomAttribute key value m) =
108 go i ind (BS.fromChar ' '
109 <> bs_ChoiceString key
110 <> BS.fromByteString "=\""
111 <> bs_ChoiceString value
114 go i ind _attrs (Content content _) =
115 if i/=IndentTagPreserve
116 then indentChoiceString ind content
117 else bs_ChoiceString content
118 go i ind _attrs (Comment comment _) =
119 (if i==IndentTagChildren then ind else mempty)
120 <> BS.fromByteString "<!--"
121 <> (if i==IndentTagChildren
122 then indentChoiceString ind
125 <> BS.fromByteString "-->"
126 go i ind attrs (Append m1 m2) =
129 go _ip _ind _ (Empty _) = mempty
132 -- | Render 'Markup' to a lazy UTF-8 encoded 'BSL.ByteString'.
133 prettyMarkup :: (Text -> IndentTag) -> Markup -> BSL.ByteString
134 prettyMarkup ind = BS.toLazyByteString . prettyMarkupBuilder ind
136 prettyMarkupIO :: (Text -> IndentTag) -> (BS.ByteString -> IO ()) -> Markup -> IO ()
137 prettyMarkupIO ind io = BS.toByteStringIO io . prettyMarkupBuilder ind
139 bs_ChoiceString :: ChoiceString -> Builder
140 bs_ChoiceString cs = BS.renderMarkupBuilder (Content cs ())
142 t_ChoiceString :: ChoiceString -> Text
143 t_ChoiceString = BS.decodeUtf8 . BS.toByteString . bs_ChoiceString
145 -- | @indentText ind txt@ indent 'tct' with 'ind' at newlines.
146 indentText :: Builder -> Text -> Builder
149 List.intersperse ind .
150 (BS.fromHtmlEscapedText <$>) .
153 -- | Render an indented 'ChoiceString'.
154 indentChoiceString :: Builder -> ChoiceString -> Builder
155 indentChoiceString ind (Static s) = indentText ind $ getText s
156 indentChoiceString ind (String s) = indentText ind $ Text.pack s
157 indentChoiceString ind (Text s) = indentText ind s
158 indentChoiceString ind (ByteString s) = indentText ind $ BS.decodeUtf8 s
159 indentChoiceString ind (PreEscaped x) = case x of
160 String s -> indentText ind $ Text.pack s
161 Text s -> indentText ind s
162 s -> indentChoiceString ind s
163 indentChoiceString ind (External x) = case x of
164 -- Check that the sequence "</" is *not* in the external data.
165 String s -> if "</" `List.isInfixOf` s then mempty else BS.fromString s
166 Text s -> if "</" `Text.isInfixOf` s then mempty else BS.fromText s
167 ByteString s -> if "</" `BS.isInfixOf` s then mempty else BS.fromByteString s
168 s -> indentChoiceString ind s
169 indentChoiceString ind (AppendChoiceString x y) =
170 indentChoiceString ind x <>
171 indentChoiceString ind y
172 indentChoiceString ind EmptyChoiceString = indentText ind mempty
173 {-# INLINE indentChoiceString #-}