@@ -1,5 +1,6 @@
1{-# LANGUAGE OverloadedStrings #-}
1{-# LANGUAGE OverloadedStrings #-}
2{-# LANGUAGE DeriveGeneric #-}
2{-# LANGUAGE DeriveGeneric #-}
3+{-# LANGUAGE LambdaCase #-}
4-- | This is a performance-oriented HTML tokenizer aim at web-crawling
5-- | This is a performance-oriented HTML tokenizer aim at web-crawling
5-- applications. It follows the HTML5 parsing specification quite closely,
6-- applications. It follows the HTML5 parsing specification quite closely,
@@ -14,6 +15,12 @@ module Text.HTML.Parser
14 , Token(..)
15 , Token(..)
15 , TagName, AttrName, AttrValue
16 , TagName, AttrName, AttrValue
16 , Attr(..)
17 , Attr(..)
18+ -- * Rendering, text canonicalization
19import Data.Char hiding (isSpace)
26import Data.Char hiding (isSpace)
@@ -277,3 +284,42 @@ parseTokensLazy = unfoldr f
277 case AL.parse token t of
284 case AL.parse token t of
278 AL.Done rest tok -> Just (tok, rest)
285 AL.Done rest tok -> Just (tok, rest)
279 _ -> Nothing
286 _ -> Nothing
288+-- | See 'renderToken'.
289+renderTokens :: [Token] -> TL.Text
290+renderTokens = mconcat . fmap renderToken
292+-- | (Somewhat) canonical string representation of 'Token'.
293+renderToken :: Token -> TL.Text
294+renderToken = TL.fromStrict . mconcat . \case
295+ (TagOpen n []) -> ["<", n, ">"]
296+ (TagOpen n attrs) -> ["<", n, " ", renderAttrs attrs, ">"]
297+ (TagClose n) -> ["</", n, ">"]
298+ (ContentChar c) -> [T.singleton c]
299+ (ContentText t) -> [t]
300+ (Comment builder) -> ["<!--", TL.toStrict $ B.toLazyText builder, "-->"]
301+ (Doctype t) -> ["<!DOCTYPE", t, ">"]
303+-- | See 'renderAttr'.
304+renderAttrs :: [Attr] -> Text
305+renderAttrs = T.unwords . fmap renderAttr . reverse
307+-- | Does not escape quotation in attribute values!
308+renderAttr :: Attr -> Text
309+renderAttr (Attr k v) = mconcat [k, "=\"", v, "\""]
311+-- | Meld neighoring 'ContentChar' and 'ContentText' constructors together and drops empty text
313+canonicalizeTokens :: [Token] -> [Token]
314+canonicalizeTokens = filter (/= ContentText "") . meldTextTokens
316+meldTextTokens :: [Token] -> [Token]
317+meldTextTokens = concatTexts . fmap charToText
319+ charToText (ContentChar c) = ContentText (T.singleton c)
323+ (ContentText t : ContentText t' : ts) -> concatTexts $ ContentText (t <> t') : ts
324+ (t : ts) -> t : concatTexts ts