blaze-from-html 0.3.2.0 → 0.3.2.1
raw patch · 7 files changed
+881/−875 lines, 7 files
Files
- Util/BlazeFromHtml.hs +0/−327
- Util/GenerateHtmlCombinators.hs +0/−469
- Util/Sanitize.hs +0/−76
- blaze-from-html.cabal +4/−3
- src/Util/BlazeFromHtml.hs +327/−0
- src/Util/GenerateHtmlCombinators.hs +470/−0
- src/Util/Sanitize.hs +80/−0
− Util/BlazeFromHtml.hs
@@ -1,327 +0,0 @@--- | A module for conversion from HTML to BlazeHtml Haskell code.----module Main where--import Control.Monad (forM_, when)-import Control.Applicative ((<$>))-import Data.List (isPrefixOf, stripPrefix)-import Data.Maybe (listToMaybe, fromMaybe, fromJust)-import Data.Char (toLower, isSpace)-import Control.Arrow (first)-import System.Environment (getArgs)-import System.FilePath (dropExtension)-import qualified Data.Map as M-import System.Console.GetOpt--import Text.HTML.TagSoup--import Util.Sanitize (sanitize)-import Util.GenerateHtmlCombinators hiding (main)---- | Simple type to represent attributes.----type Attributes = [(String, String)]---- | Intermediate tree representation. This representation contains several--- constructors aimed at pretty-printing.----data Html = Parent String Attributes Html- | Block [Html]- | Text String- | Comment String- | Doctype- deriving (Show)---- | Different combinator types.----data CombinatorType = ParentCombinator- | LeafCombinator- | UnknownCombinator- deriving (Eq, Show)---- | Traverse the list of tags to produce an intermediate representation of the--- HTML tree.----makeTree :: HtmlVariant -- ^ HTML variant used- -> Bool -- ^ Should ignore errors- -> [String] -- ^ Stack of open tags- -> [Tag String] -- ^ Tags to parse- -> (Html, [Tag String]) -- ^ (Result, unparsed part)-makeTree _ ignore stack []- | null stack || ignore = (Block [], [])- | otherwise = error $ "Error: tags left open at the end: " ++ show stack-makeTree variant ignore stack (TagPosition row _ : x : xs) = case x of- TagOpen tag attrs -> if toLower' tag == "!doctype"- then addHtml Doctype xs- else let tag' = toLower' tag- (inner, t) = case combinatorType variant tag' of- LeafCombinator -> (Block [], xs)- _ -> makeTree variant ignore (tag' : stack) xs- p = Parent tag' (map (first toLower') attrs) inner- in addHtml p t- -- The closing tag must match the stack. If it is a closing leaf, we can- -- ignore it- TagClose tag ->- let isLeafCombinator = combinatorType variant tag == LeafCombinator- matchesStack = listToMaybe stack == Just (toLower' tag)- in case (isLeafCombinator, matchesStack, ignore) of- -- It's a leaf combinator, don't care about this element- (True, _, _) -> makeTree variant ignore stack xs- -- It's a parent and the stack doesn't match- (False, False, False) -> error $- "Line " ++ show row ++ ": " ++ show tag ++ " closed but "- ++ show stack ++ " should be closed instead."- -- Stack might not match but we ignore it anyway- (False, _, _) -> (Block [], xs)- TagText text -> addHtml (Text text) xs- TagComment comment -> addHtml (Comment comment) xs- _ -> makeTree variant ignore stack xs- where- addHtml html xs' = let (Block l, r) = makeTree variant ignore stack xs'- in (Block (html : l), r)-- toLower' = map toLower-makeTree _ _ _ _ = error "TagSoup error"---- | Remove empty text from the HTML.----removeEmptyText :: Html -> Html-removeEmptyText (Block b) = Block $ map removeEmptyText $ flip filter b $ \h ->- case h of Text text -> any (not . isSpace) text- _ -> True-removeEmptyText (Parent tag attrs inner) =- Parent tag attrs $ removeEmptyText inner-removeEmptyText x = x---- | Try to eliminiate Block constructors as much as possible.----minimizeBlocks :: Html -> Html-minimizeBlocks (Parent t a (Block [x])) = minimizeBlocks $ Parent t a x-minimizeBlocks (Parent t a x) = Parent t a $ minimizeBlocks x-minimizeBlocks (Block x) = Block $ map minimizeBlocks x-minimizeBlocks x = x---- | Get the type of a combinator, using a given variant.----combinatorType :: HtmlVariant -> String -> CombinatorType-combinatorType variant combinator- | combinator == "docTypeHtml" = ParentCombinator- | combinator `elem` parents variant = ParentCombinator- | combinator `elem` leafs variant = LeafCombinator- | otherwise = UnknownCombinator---- | Create a special @<html>@ parent that includes the docype.----joinHtmlDoctype :: Html -> Html-joinHtmlDoctype (Block (Doctype : Parent "html" attrs inner : xs)) =- Block $ Parent "docTypeHtml" attrs inner : xs-joinHtmlDoctype x = x---- | Produce the Blaze code from the HTML. The result is a list of lines.----fromHtml :: HtmlVariant -- ^ Used HTML variant- -> Bool -- ^ Should ignore errors- -> Html -- ^ HTML tree- -> [String] -- ^ Resulting lines of code-fromHtml _ _ Doctype = ["docType"]-fromHtml _ _ (Text text) = ["\"" ++ concatMap escape (trim text) ++ "\""]- where- -- Remove whitespace on both ends of a string- trim = reverse . dropWhile isSpace . reverse . dropWhile isSpace- -- Escape a number of characters- escape '"' = "\\\""- escape '\n' = "\\n"- escape x = [x]-fromHtml _ _ (Comment comment) = map ("-- " ++) $ lines comment-fromHtml variant ignore (Block block) =- concatMap (fromHtml variant ignore) block-fromHtml variant ignore (Parent tag attrs inner) =- case combinatorType variant tag of- -- Actual parent tags- ParentCombinator -> case inner of- (Block ls) -> if null ls- then [combinator ++- (if null attrs then " " else " $ ") ++ "mempty"]- else (combinator ++ " $ do") :- indent (fromHtml variant ignore inner)- -- We join non-block parents for better readability.- x -> let ls = fromHtml variant ignore x- apply = if dropApply x then " " else " $ "- in case ls of (y : ys) -> (combinator ++ apply ++ y) : ys- [] -> [combinator]-- -- Leaf tags- LeafCombinator -> [combinator]-- -- Unknown tag- UnknownCombinator -> if ignore- then fromHtml variant ignore inner- else error $ "Tag " ++ tag ++ " is illegal in "- ++ show variant- where- combinator = qualifiedSanitize "H." tag ++ attributes'- attributes' = attrs >>= \(k, v) -> case k `elem` attributes variant of- True -> " ! " ++ qualifiedSanitize "A." k ++ " " ++ show v- False- | "data-" `isPrefixOf` k -> " ! "- ++ "dataAttribute" ++ " "- ++ show (fromJust $ stripPrefix "data-" k)- ++ " " ++ show v- | ignore -> ""- | otherwise -> error $ "Attribute "- ++ k ++ " is illegal in "- ++ show variant-- -- Qualifies a tag with the given qualifier if needed, and sanitizes it.- qualifiedSanitize qualifier tag' =- (if isNameClash variant tag' then qualifier else "") ++ sanitize tag'-- -- Check if we can drop the apply operator ($), for readability reasons.- -- This would change:- --- -- > p $ "Some text"- --- -- Into- --- -- > p "Some text"- --- dropApply (Parent _ _ _) = False- dropApply (Block _) = False- dropApply _ = null attrs---- | Produce the code needed for initial imports.----getImports :: HtmlVariant -> [String]-getImports variant =- [ "{-# LANGUAGE OverloadedStrings #-}"- , ""- , import_ "Prelude"- , qualify "Prelude" "P"- , import_ "Data.Monoid (mempty)"- , ""- , import_ h- , qualify h "H"- , import_ a- , qualify a "A"- ]- where- import_ = ("import " ++)- qualify name short = "import qualified " ++ name ++ " as " ++ short- h = getModuleName variant- a = getAttributeModuleName variant---- | Convert the HTML to blaze code.----blazeFromHtml :: HtmlVariant -- ^ Variant to use- -> Bool -- ^ Produce standalone code- -> Bool -- ^ Should we ignore errors- -> String -- ^ Template name- -> String -- ^ HTML code- -> String -- ^ Resulting code-blazeFromHtml variant standalone ignore name =- unlines . addSignature . fromHtml variant ignore- . joinHtmlDoctype . minimizeBlocks- . removeEmptyText . fst . makeTree variant ignore []- . parseTagsOptions parseOptions { optTagPosition = True }- where- addSignature body = if standalone then [ name ++ " :: Html"- , name ++ " = do"- ] ++ indent body- else body---- | Indent block of code.----indent :: [String] -> [String]-indent = map (" " ++)---- | Main function----main :: IO ()-main = do- args <- getOpt Permute options <$> getArgs- case args of- (o, n, []) -> let v = getVariant o- s = standalone' o- i = ignore' o- in do imports' v o- main' v s i n- (_, _, _) -> putStr help- where- -- No files given, work with stdin- main' variant standalone ignore [] = interact $- blazeFromHtml variant standalone ignore "template"-- -- Handle all files- main' variant standalone ignore files = forM_ files $ \file -> do- body <- readFile file- putStrLn $ blazeFromHtml variant standalone ignore- (dropExtension file) body-- -- Print imports if needed- imports' variant opts = when (standalone' opts) $- putStrLn $ unlines $ getImports variant-- -- Should we produce standalone code?- standalone' opts = ArgStandalone `elem` opts-- -- Should we ignore errors?- ignore' opts = ArgIgnoreErrors `elem` opts-- -- Get the variant from the options- getVariant opts = fromMaybe defaultHtmlVariant $ listToMaybe $- flip concatMap opts $ \o -> case o of (ArgHtmlVariant x) -> [x]- _ -> []---- | Help information.----help :: String-help = unlines $- [ "This is a tool to convert HTML code to BlazeHtml code. It is still"- , "experimental and the results might need to be edited manually."- , ""- , "USAGE"- , ""- , " blaze-from-html [OPTIONS...] [FILES ...]"- , ""- , "When no files are given, it works as a filter."- , ""- , "EXAMPLE"- , ""- , " blaze-from-html -v html4-strict index.html"- , ""- , "This converts the index.html file to Haskell code, writing to stdout."- , ""- , "OPTIONS"- , usageInfo "" options- , "VARIANTS"- , ""- ] ++- map ((" " ++) . fst) (M.toList htmlVariants) ++- [ ""- , "By default, " ++ show defaultHtmlVariant ++ " is used."- ]---- | Options for the CLI program----data Arg = ArgHtmlVariant HtmlVariant- | ArgStandalone- | ArgIgnoreErrors- deriving (Show, Eq)---- | A description of the options----options :: [OptDescr Arg]-options =- [ Option "v" ["html-variant"] htmlVariantOption "HTML variant to use"- , Option "s" ["standalone"] (NoArg ArgStandalone) "Produce standalone code"- , Option "e" ["ignore-errors"] (NoArg ArgIgnoreErrors) "Ignore most errors"- ]- where- htmlVariantOption = flip ReqArg "VARIANT" $ \name -> ArgHtmlVariant $- fromMaybe (error $ "No HTML variant called " ++ name ++ " found.")- (M.lookup name htmlVariants)---- | The default HTML variant----defaultHtmlVariant :: HtmlVariant-defaultHtmlVariant = html5
− Util/GenerateHtmlCombinators.hs
@@ -1,469 +0,0 @@-{-# LANGUAGE CPP #-}--#define DO_NOT_EDIT (doNotEdit __FILE__ __LINE__)---- | Generates code for HTML tags.----module Util.GenerateHtmlCombinators where--import Control.Arrow ((&&&))-import Data.List (sort, sortBy, intersperse, intercalate)-import Data.Ord (comparing)-import System.Directory (createDirectoryIfMissing)-import System.FilePath ((</>), (<.>))-import Data.Map (Map)-import qualified Data.Map as M-import Data.Char (toLower)-import qualified Data.Set as S--import Util.Sanitize (sanitize, prelude)---- | Datatype for an HTML variant.----data HtmlVariant = HtmlVariant- { version :: [String]- , docType :: [String]- , parents :: [String]- , leafs :: [String]- , attributes :: [String]- , selfClosing :: Bool- } deriving (Eq)--instance Show HtmlVariant where- show = map toLower . intercalate "-" . version---- | Get the full module name for an HTML variant.----getModuleName :: HtmlVariant -> String-getModuleName = ("Text.Blaze." ++) . intercalate "." . version---- | Get the attribute module name for an HTML variant.----getAttributeModuleName :: HtmlVariant -> String-getAttributeModuleName = (++ ".Attributes") . getModuleName---- | Check if a given name causes a name clash.----isNameClash :: HtmlVariant -> String -> Bool-isNameClash v t- -- Both an element and an attribute- | (t `elem` parents v || t `elem` leafs v) && t `elem` attributes v = True- -- Already a prelude function- | sanitize t `S.member` prelude = True- | otherwise = False---- | Write an HTML variant.----writeHtmlVariant :: HtmlVariant -> IO ()-writeHtmlVariant htmlVariant = do- -- Make a directory.- createDirectoryIfMissing True basePath-- let tags = zip parents' (repeat makeParent)- ++ zip leafs' (repeat (makeLeaf $ selfClosing htmlVariant))- sortedTags = sortBy (comparing fst) tags- appliedTags = map (\(x, f) -> f x) sortedTags-- -- Write the main module.- writeFile' (basePath <.> "hs") $ removeTrailingNewlines $ unlines- [ DO_NOT_EDIT- , "{-# LANGUAGE OverloadedStrings #-}"- , "-- | This module exports HTML combinators used to create documents."- , "--"- , exportList modulName $ "module Text.Blaze"- : "docType"- : "docTypeHtml"- : map (sanitize . fst) sortedTags- , DO_NOT_EDIT- , "import Prelude ((>>), (.))"- , ""- , "import Text.Blaze"- , "import Text.Blaze.Internal"- , ""- , makeDocType $ docType htmlVariant- , makeDocTypeHtml $ docType htmlVariant- , unlines appliedTags- ]-- let sortedAttributes = sort attributes'-- -- Write the attribute module.- writeFile' (basePath </> "Attributes.hs") $ removeTrailingNewlines $ unlines- [ DO_NOT_EDIT- , "-- | This module exports combinators that provide you with the"- , "-- ability to set attributes on HTML elements."- , "--"- , "{-# LANGUAGE OverloadedStrings #-}"- , exportList attributeModuleName $ map sanitize sortedAttributes- , DO_NOT_EDIT- , "import Prelude ()"- , ""- , "import Text.Blaze.Internal (Attribute, AttributeValue, attribute)"- , ""- , unlines (map makeAttribute sortedAttributes)- ]- where- basePath = "Text" </> "Blaze" </> foldl1 (</>) version'- modulName = getModuleName htmlVariant- attributeModuleName = getAttributeModuleName htmlVariant- attributes' = attributes htmlVariant- parents' = parents htmlVariant- leafs' = leafs htmlVariant- version' = version htmlVariant- removeTrailingNewlines = reverse . drop 2 . reverse- writeFile' file content = do- putStrLn ("Generating " ++ file)- writeFile file content---- | Create a string, consisting of @x@ spaces, where @x@ is the length of the--- argument.----spaces :: String -> String-spaces = flip replicate ' ' . length---- | Join blocks of code with a newline in between.----unblocks :: [String] -> String-unblocks = unlines . intersperse "\n"---- | A warning to not edit the generated code.----doNotEdit :: FilePath -> Int -> String-doNotEdit fileName lineNumber = init $ unlines- [ "-- WARNING: The next block of code was automatically generated by"- , "-- " ++ fileName ++ ":" ++ show lineNumber- , "--"- ]---- | Generate an export list for a Haskell module.----exportList :: String -- ^ Module name.- -> [String] -- ^ List of functions.- -> String -- ^ Resulting string.-exportList _ [] = error "exportList without functions."-exportList name (f:functions) = unlines $- [ "module " ++ name- , " ( " ++ f- ] ++- map (" , " ++) functions ++- [ " ) where"]---- | Generate a function for a doctype.----makeDocType :: [String] -> String-makeDocType lines' = unlines- [ DO_NOT_EDIT- , "-- | Combinator for the document type. This should be placed at the top"- , "-- of every HTML page."- , "--"- , "-- Example:"- , "--"- , "-- > docType"- , "--"- , "-- Result:"- , "--"- , unlines (map ("-- > " ++) lines') ++ "--"- , "docType :: Html -- ^ The document type HTML."- , "docType = preEscapedText " ++ show (unlines lines')- , "{-# INLINE docType #-}"- ]---- | Generate a function for the HTML tag (including the doctype).----makeDocTypeHtml :: [String] -- ^ The doctype.- -> String -- ^ Resulting combinator function.-makeDocTypeHtml lines' = unlines- [ DO_NOT_EDIT- , "-- | Combinator for the @\\<html>@ element. This combinator will also"- , "-- insert the correct doctype."- , "--"- , "-- Example:"- , "--"- , "-- > docTypeHtml $ span $ text \"foo\""- , "--"- , "-- Result:"- , "--"- , unlines (map ("-- > " ++) lines') ++ "-- > <html><span>foo</span></html>"- , "--"- , "docTypeHtml :: Html -- ^ Inner HTML."- , " -> Html -- ^ Resulting HTML."- , "docTypeHtml inner = docType >> html inner"- , "{-# INLINE docTypeHtml #-}"- ]---- | Generate a function for an HTML tag that can be a parent.----makeParent :: String -> String-makeParent tag = unlines- [ DO_NOT_EDIT- , "-- | Combinator for the @\\<" ++ tag ++ ">@ element."- , "--"- , "-- Example:"- , "--"- , "-- > " ++ function ++ " $ span $ text \"foo\""- , "--"- , "-- Result:"- , "--"- , "-- > <" ++ tag ++ "><span>foo</span></" ++ tag ++ ">"- , "--"- , function ++ " :: Html -- ^ Inner HTML."- , spaces function ++ " -> Html -- ^ Resulting HTML."- , function ++ " = Parent \"" ++ tag ++ "\" \"<" ++ tag- ++ "\" \"</" ++ tag ++ ">\"" ++ modifier- , "{-# INLINE " ++ function ++ " #-}"- ]- where- function = sanitize tag- modifier = if tag `elem` ["style", "script"] then " . external" else ""---- | Generate a function for an HTML tag that must be a leaf.----makeLeaf :: Bool -- ^ Make leaf tags self-closing- -> String -- ^ Tag for the combinator- -> String -- ^ Combinator code-makeLeaf closing tag = unlines- [ DO_NOT_EDIT- , "-- | Combinator for the @\\<" ++ tag ++ " />@ element."- , "--"- , "-- Example:"- , "--"- , "-- > " ++ function- , "--"- , "-- Result:"- , "--"- , "-- > <" ++ tag ++ " />"- , "--"- , function ++ " :: Html -- ^ Resulting HTML."- , function ++ " = Leaf \"" ++ tag ++ "\" \"<" ++ tag ++ "\" " ++ "\""- ++ (if closing then " /" else "") ++ ">\""- , "{-# INLINE " ++ function ++ " #-}"- ]- where- function = sanitize tag---- | Generate a function for an HTML attribute.----makeAttribute :: String -> String-makeAttribute name = unlines- [ DO_NOT_EDIT- , "-- | Combinator for the @" ++ name ++ "@ attribute."- , "--"- , "-- Example:"- , "--"- , "-- > div ! " ++ function ++ " \"bar\" $ \"Hello.\""- , "--"- , "-- Result:"- , "--"- , "-- > <div " ++ name ++ "=\"bar\">Hello.</div>"- , "--"- , function ++ " :: AttributeValue -- ^ Attribute value."- , spaces function ++ " -> Attribute -- ^ Resulting attribute."- , function ++ " = attribute \"" ++ name ++ "\" \" "- ++ name ++ "=\\\"\""- , "{-# INLINE " ++ function ++ " #-}"- ]- where- function = sanitize name---- | HTML 4.01 Strict.--- A good reference can be found here: http://www.w3schools.com/tags/default.asp----html4Strict :: HtmlVariant-html4Strict = HtmlVariant- { version = ["Html4", "Strict"]- , docType =- [ "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\""- , " \"http://www.w3.org/TR/html4/strict.dtd\">"- ]- , parents =- [ "a", "abbr", "acronym", "address", "b", "bdo", "big", "blockquote"- , "body" , "button", "caption", "cite", "code", "colgroup", "dd", "del"- , "dfn", "div" , "dl", "dt", "em", "fieldset", "form", "h1", "h2", "h3"- , "h4", "h5", "h6", "head", "html", "i", "ins" , "kbd", "label"- , "legend", "li", "map", "noscript", "object", "ol", "optgroup"- , "option", "p", "pre", "q", "samp", "script", "select", "small"- , "span", "strong", "style", "sub", "sup", "table", "tbody", "td"- , "textarea", "tfoot", "th", "thead", "title", "tr", "tt", "ul", "var"- ]- , leafs =- [ "area", "br", "col", "hr", "link", "img", "input", "meta", "param"- ]- , attributes =- [ "abbr", "accept", "accesskey", "action", "align", "alt", "archive"- , "axis", "border", "cellpadding", "cellspacing", "char", "charoff"- , "charset", "checked", "cite", "class", "classid", "codebase"- , "codetype", "cols", "colspan", "content", "coords", "data", "datetime"- , "declare", "defer", "dir", "disabled", "enctype", "for", "frame"- , "headers", "height", "href", "hreflang", "http-equiv", "id", "label"- , "lang", "maxlength", "media", "method", "multiple", "name", "nohref"- , "onabort", "onblur", "onchange", "onclick", "ondblclick", "onfocus"- , "onkeydown", "onkeypress", "onkeyup", "onload", "onmousedown"- , "onmousemove", "onmouseout", "onmouseover", "onmouseup", "onreset"- , "onselect", "onsubmit", "onunload", "profile", "readonly", "rel"- , "rev", "rows", "rowspan", "rules", "scheme", "scope", "selected"- , "shape", "size", "span", "src", "standby", "style", "summary"- , "tabindex", "title", "type", "usemap", "valign", "value", "valuetype"- , "width"- ]- , selfClosing = False- }---- | HTML 4.0 Transitional----html4Transitional :: HtmlVariant-html4Transitional = HtmlVariant- { version = ["Html4", "Transitional"]- , docType =- [ "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\""- , " \"http://www.w3.org/TR/html4/loose.dtd\">"- ]- , parents = parents html4Strict ++- [ "applet", "center", "dir", "font", "iframe", "isindex", "menu"- , "noframes", "s", "u"- ]- , leafs = leafs html4Strict ++ ["basefont"]- , attributes = attributes html4Strict ++- [ "background", "bgcolor", "clear", "compact", "hspace", "language"- , "noshade", "nowrap", "start", "target", "vspace"- ]- , selfClosing = False- }---- | HTML 4.0 FrameSet----html4FrameSet :: HtmlVariant-html4FrameSet = HtmlVariant- { version = ["Html4", "FrameSet"]- , docType =- [ "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 FrameSet//EN\""- , " \"http://www.w3.org/TR/html4/frameset.dtd\">"- ]- , parents = parents html4Transitional ++ ["frameset"]- , leafs = leafs html4Transitional ++ ["frame"]- , attributes = attributes html4Transitional ++- [ "frameborder", "scrolling"- ]- , selfClosing = False- }---- | XHTML 1.0 Strict----xhtml1Strict :: HtmlVariant-xhtml1Strict = HtmlVariant- { version = ["XHtml1", "Strict"]- , docType =- [ "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\""- , " \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">"- ]- , parents = parents html4Strict- , leafs = leafs html4Strict- , attributes = attributes html4Strict- , selfClosing = True- }---- | XHTML 1.0 Transitional----xhtml1Transitional :: HtmlVariant-xhtml1Transitional = HtmlVariant- { version = ["XHtml1", "Transitional"]- , docType =- [ "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\""- , " \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">"- ]- , parents = parents html4Transitional- , leafs = leafs html4Transitional- , attributes = attributes html4Transitional- , selfClosing = True- }---- | XHTML 1.0 FrameSet----xhtml1FrameSet :: HtmlVariant-xhtml1FrameSet = HtmlVariant- { version = ["XHtml1", "FrameSet"]- , docType =- [ "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 FrameSet//EN\""- , " \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd\">"- ]- , parents = parents html4FrameSet- , leafs = leafs html4FrameSet- , attributes = attributes html4FrameSet- , selfClosing = True- }---- | HTML 5.0--- A good reference can be found here:--- http://www.w3schools.com/html5/html5_reference.asp----html5 :: HtmlVariant-html5 = HtmlVariant- { version = ["Html5"]- , docType = ["<!DOCTYPE HTML>"]- , parents =- [ "a", "abbr", "address", "article", "aside", "audio", "b", "base"- , "bdo", "blockquote", "body", "button", "canvas", "caption", "cite"- , "code", "colgroup", "command", "datalist", "dd", "del", "details"- , "dfn", "div", "dl", "dt", "em", "fieldset", "figcaption", "figure"- , "footer", "form", "h1", "h2", "h3", "h4", "h5", "h6", "head", "header"- , "hgroup", "html", "i", "iframe", "ins", "keygen", "kbd", "label"- , "legend", "li", "map", "mark", "menu", "meter", "nav", "noscript"- , "object", "ol", "optgroup", "option", "output", "p", "pre", "progress"- , "q", "rp", "rt", "ruby", "samp", "script", "section", "select"- , "small", "source", "span", "strong", "style", "sub", "summary", "sup"- , "table", "tbody", "td", "textarea", "tfoot", "th", "thead", "time"- , "title", "tr", "ul", "var", "video"- ]- , leafs =- [ "area", "br", "col", "embed", "hr", "img", "input", "meta", "link"- , "param"- ]- , attributes =- [ "accept", "accept-charset", "accesskey", "action", "alt", "async"- , "autocomplete", "autofocus", "autoplay", "challenge", "charset"- , "checked", "cite", "class", "cols", "colspan", "content"- , "contenteditable", "contextmenu", "controls", "coords", "data"- , "datetime", "defer", "dir", "disabled", "draggable", "enctype", "for"- , "form", "formaction", "formenctype", "formmethod", "formnovalidate"- , "formtarget", "headers", "height", "hidden", "high", "href"- , "hreflang", "http-equiv", "icon", "id", "ismap", "item", "itemprop"- , "keytype", "label", "lang", "list", "loop", "low", "manifest", "max"- , "maxlength", "media", "method", "min", "multiple", "name"- , "novalidate", "onbeforeonload", "onbeforeprint", "onblur", "oncanplay"- , "oncanplaythrough", "onchange", "oncontextmenu", "onclick"- , "ondblclick", "ondrag", "ondragend", "ondragenter", "ondragleave"- , "ondragover", "ondragstart", "ondrop", "ondurationchange", "onemptied"- , "onended", "onerror", "onfocus", "onformchange", "onforminput"- , "onhaschange", "oninput", "oninvalid", "onkeydown", "onkeyup"- , "onload", "onloadeddata", "onloadedmetadata", "onloadstart"- , "onmessage", "onmousedown", "onmousemove", "onmouseout", "onmouseover"- , "onmouseup", "onmousewheel", "ononline", "onpagehide", "onpageshow"- , "onpause", "onplay", "onplaying", "onprogress", "onpropstate"- , "onratechange", "onreadystatechange", "onredo", "onresize", "onscroll"- , "onseeked", "onseeking", "onselect", "onstalled", "onstorage"- , "onsubmit", "onsuspend", "ontimeupdate", "onundo", "onunload"- , "onvolumechange", "onwaiting", "open", "optimum", "pattern", "ping"- , "placeholder", "preload", "pubdate", "radiogroup", "readonly", "rel"- , "required", "reversed", "rows", "rowspan", "sandbox", "scope"- , "scoped", "seamless", "selected", "shape", "size", "sizes", "span"- , "spellcheck", "src", "srcdoc", "start", "step", "style", "subject"- , "summary", "tabindex", "target", "title", "type", "usemap", "value"- , "width", "wrap", "xmlns"- ]- , selfClosing = False- }---- | A map of HTML variants, per version, lowercase.----htmlVariants :: Map String HtmlVariant-htmlVariants = M.fromList $ map (show &&& id)- [ html4Strict- , html4Transitional- , html4FrameSet- , xhtml1Strict- , xhtml1Transitional- , xhtml1FrameSet- , html5- ]--main :: IO ()-main = mapM_ (writeHtmlVariant . snd) $ M.toList htmlVariants
− Util/Sanitize.hs
@@ -1,76 +0,0 @@--- | A program to sanitize an HTML tag to a Haskell function.----module Util.Sanitize- ( sanitize- , keywords- , prelude- ) where--import Data.Char (toLower, toUpper)-import Data.Set (Set)-import qualified Data.Set as S---- | Sanitize a tag. This function returns a name that can be used as--- combinator in haskell source code.------ Examples:------ > sanitize "class" == "class_"--- > sanitize "http-equiv" == "httpEquiv"----sanitize :: String -> String-sanitize = appendUnderscore . removeDash . map toLower- where- -- Remove a dash, replacing it by camelcase notation- --- -- Example:- --- -- > removeDash "foo-bar" == "fooBar"- --- removeDash ('-' : x : xs) = toUpper x : removeDash xs- removeDash (x : xs) = x : removeDash xs- removeDash [] = []-- appendUnderscore t | t `S.member` keywords = t ++ "_"- | otherwise = t---- | A set of standard Haskell keywords, which cannot be used as combinators.----keywords :: Set String-keywords = S.fromList- [ "case", "class", "data", "default", "deriving", "do", "else", "if"- , "import", "in", "infix", "infixl", "infixr", "instance" , "let", "module"- , "newtype", "of", "then", "type", "where"- ]---- | Set of functions from the Prelude, which we do not use as combinators.----prelude :: Set String-prelude = S.fromList- [ "abs", "acos", "acosh", "all", "and", "any", "appendFile", "asTypeOf"- , "asin", "asinh", "atan", "atan2", "atanh", "break", "catch", "ceiling"- , "compare", "concat", "concatMap", "const", "cos", "cosh", "curry", "cycle"- , "decodeFloat", "div", "divMod", "drop", "dropWhile", "either", "elem"- , "encodeFloat", "enumFrom", "enumFromThen", "enumFromThenTo", "enumFromTo"- , "error", "even", "exp", "exponent", "fail", "filter", "flip"- , "floatDigits", "floatRadix", "floatRange", "floor", "fmap", "foldl"- , "foldl1", "foldr", "foldr1", "fromEnum", "fromInteger", "fromIntegral"- , "fromRational", "fst", "gcd", "getChar", "getContents", "getLine", "head"- , "id", "init", "interact", "ioError", "isDenormalized", "isIEEE"- , "isInfinite", "isNaN", "isNegativeZero", "iterate", "last", "lcm"- , "length", "lex", "lines", "log", "logBase", "lookup", "map", "mapM"- , "mapM_", "max", "maxBound", "maximum", "maybe", "min", "minBound"- , "minimum", "mod", "negate", "not", "notElem", "null", "odd", "or"- , "otherwise", "pi", "pred", "print", "product", "properFraction", "putChar"- , "putStr", "putStrLn", "quot", "quotRem", "read", "readFile", "readIO"- , "readList", "readLn", "readParen", "reads", "readsPrec", "realToFrac"- , "recip", "rem", "repeat", "replicate", "return", "reverse", "round"- , "scaleFloat", "scanl", "scanl1", "scanr", "scanr1", "seq", "sequence"- , "sequence_", "show", "showChar", "showList", "showParen", "showString"- , "shows", "showsPrec", "significand", "signum", "sin", "sinh", "snd"- , "span", "splitAt", "sqrt", "subtract", "succ", "sum", "tail", "take"- , "takeWhile", "tan", "tanh", "toEnum", "toInteger", "toRational"- , "truncate", "uncurry", "undefined", "unlines", "until", "unwords", "unzip"- , "unzip3", "userError", "words", "writeFile", "zip", "zip3", "zipWith"- , "zipWith3"- ]
blaze-from-html.cabal view
@@ -1,5 +1,5 @@ Name: blaze-from-html-Version: 0.3.2.0+Version: 0.3.2.1 Synopsis: Tool to convert HTML to BlazeHtml code. Description: Tool that converts HTML files to Haskell code, ready to be used with the BlazeHtml library.@@ -15,8 +15,9 @@ Cabal-version: >= 1.6 Executable blaze-from-html- Ghc-Options: -Wall- Main-Is: Util/BlazeFromHtml.hs+ Ghc-Options: -Wall+ Hs-source-dirs: src+ Main-Is: Util/BlazeFromHtml.hs Other-Modules: Util.GenerateHtmlCombinators
+ src/Util/BlazeFromHtml.hs view
@@ -0,0 +1,327 @@+-- | A module for conversion from HTML to BlazeHtml Haskell code.+--+module Main where++import Control.Monad (forM_, when)+import Control.Applicative ((<$>))+import Data.List (stripPrefix)+import Data.Maybe (listToMaybe, fromMaybe)+import Data.Char (toLower, isSpace)+import Control.Arrow (first)+import System.Environment (getArgs)+import System.FilePath (dropExtension)+import qualified Data.Map as M+import System.Console.GetOpt++import Text.HTML.TagSoup++import Util.Sanitize (sanitize)+import Util.GenerateHtmlCombinators hiding (main)++-- | Simple type to represent attributes.+--+type Attributes = [(String, String)]++-- | Intermediate tree representation. This representation contains several+-- constructors aimed at pretty-printing.+--+data Html = Parent String Attributes Html+ | Block [Html]+ | Text String+ | Comment String+ | Doctype+ deriving (Show)++-- | Different combinator types.+--+data CombinatorType = ParentCombinator+ | LeafCombinator+ | UnknownCombinator+ deriving (Eq, Show)++-- | Traverse the list of tags to produce an intermediate representation of the+-- HTML tree.+--+makeTree :: HtmlVariant -- ^ HTML variant used+ -> Bool -- ^ Should ignore errors+ -> [String] -- ^ Stack of open tags+ -> [Tag String] -- ^ Tags to parse+ -> (Html, [Tag String]) -- ^ (Result, unparsed part)+makeTree _ ignore stack []+ | null stack || ignore = (Block [], [])+ | otherwise = error $ "Error: tags left open at the end: " ++ show stack+makeTree variant ignore stack (TagPosition row _ : x : xs) = case x of+ TagOpen tag attrs -> if toLower' tag == "!doctype"+ then addHtml Doctype xs+ else let tag' = toLower' tag+ (inner, t) = case combinatorType variant tag' of+ LeafCombinator -> (Block [], xs)+ _ -> makeTree variant ignore (tag' : stack) xs+ p = Parent tag' (map (first toLower') attrs) inner+ in addHtml p t+ -- The closing tag must match the stack. If it is a closing leaf, we can+ -- ignore it+ TagClose tag ->+ let isLeafCombinator = combinatorType variant tag == LeafCombinator+ matchesStack = listToMaybe stack == Just (toLower' tag)+ in case (isLeafCombinator, matchesStack, ignore) of+ -- It's a leaf combinator, don't care about this element+ (True, _, _) -> makeTree variant ignore stack xs+ -- It's a parent and the stack doesn't match+ (False, False, False) -> error $+ "Line " ++ show row ++ ": " ++ show tag ++ " closed but "+ ++ show stack ++ " should be closed instead."+ -- Stack might not match but we ignore it anyway+ (False, _, _) -> (Block [], xs)+ TagText text -> addHtml (Text text) xs+ TagComment comment -> addHtml (Comment comment) xs+ _ -> makeTree variant ignore stack xs+ where+ addHtml html xs' = let (Block l, r) = makeTree variant ignore stack xs'+ in (Block (html : l), r)++ toLower' = map toLower+makeTree _ _ _ _ = error "TagSoup error"++-- | Remove empty text from the HTML.+--+removeEmptyText :: Html -> Html+removeEmptyText (Block b) = Block $ map removeEmptyText $ flip filter b $ \h ->+ case h of Text text -> any (not . isSpace) text+ _ -> True+removeEmptyText (Parent tag attrs inner) =+ Parent tag attrs $ removeEmptyText inner+removeEmptyText x = x++-- | Try to eliminiate Block constructors as much as possible.+--+minimizeBlocks :: Html -> Html+minimizeBlocks (Parent t a (Block [x])) = minimizeBlocks $ Parent t a x+minimizeBlocks (Parent t a x) = Parent t a $ minimizeBlocks x+minimizeBlocks (Block x) = Block $ map minimizeBlocks x+minimizeBlocks x = x++-- | Get the type of a combinator, using a given variant.+--+combinatorType :: HtmlVariant -> String -> CombinatorType+combinatorType variant combinator+ | combinator == "docTypeHtml" = ParentCombinator+ | combinator `elem` parents variant = ParentCombinator+ | combinator `elem` leafs variant = LeafCombinator+ | otherwise = UnknownCombinator++-- | Create a special @<html>@ parent that includes the docype.+--+joinHtmlDoctype :: Html -> Html+joinHtmlDoctype (Block (Doctype : Parent "html" attrs inner : xs)) =+ Block $ Parent "docTypeHtml" attrs inner : xs+joinHtmlDoctype x = x++-- | Produce the Blaze code from the HTML. The result is a list of lines.+--+fromHtml :: HtmlVariant -- ^ Used HTML variant+ -> Bool -- ^ Should ignore errors+ -> Html -- ^ HTML tree+ -> [String] -- ^ Resulting lines of code+fromHtml _ _ Doctype = ["docType"]+fromHtml _ _ (Text text) = ["\"" ++ concatMap escape (trim text) ++ "\""]+ where+ -- Remove whitespace on both ends of a string+ trim = reverse . dropWhile isSpace . reverse . dropWhile isSpace+ -- Escape a number of characters+ escape '"' = "\\\""+ escape '\n' = "\\n"+ escape x = [x]+fromHtml _ _ (Comment comment) = map ("-- " ++) $ lines comment+fromHtml variant ignore (Block block) =+ concatMap (fromHtml variant ignore) block+fromHtml variant ignore (Parent tag attrs inner) =+ case combinatorType variant tag of+ -- Actual parent tags+ ParentCombinator -> case inner of+ (Block ls) -> if null ls+ then [combinator +++ (if null attrs then " " else " $ ") ++ "mempty"]+ else (combinator ++ " $ do") :+ indent (fromHtml variant ignore inner)+ -- We join non-block parents for better readability.+ x -> let ls = fromHtml variant ignore x+ apply = if dropApply x then " " else " $ "+ in case ls of (y : ys) -> (combinator ++ apply ++ y) : ys+ [] -> [combinator]++ -- Leaf tags+ LeafCombinator -> [combinator]++ -- Unknown tag+ UnknownCombinator -> if ignore+ then fromHtml variant ignore inner+ else error $ "Tag " ++ tag ++ " is illegal in "+ ++ show variant+ where+ combinator = qualifiedSanitize "H." tag ++ attributes'+ attributes' = attrs >>= \(k, v) -> case k `elem` attributes variant of+ True -> " ! " ++ qualifiedSanitize "A." k ++ " " ++ show v+ False -> case stripPrefix "data-" k of+ Just prefix -> " ! "+ ++ "dataAttribute" ++ " "+ ++ show prefix+ ++ " " ++ show v+ Nothing | ignore -> ""+ | otherwise -> error $ "Attribute "+ ++ k ++ " is illegal in "+ ++ show variant++ -- Qualifies a tag with the given qualifier if needed, and sanitizes it.+ qualifiedSanitize qualifier tag' =+ (if isNameClash variant tag' then qualifier else "") ++ sanitize tag'++ -- Check if we can drop the apply operator ($), for readability reasons.+ -- This would change:+ --+ -- > p $ "Some text"+ --+ -- Into+ --+ -- > p "Some text"+ --+ dropApply (Parent _ _ _) = False+ dropApply (Block _) = False+ dropApply _ = null attrs++-- | Produce the code needed for initial imports.+--+getImports :: HtmlVariant -> [String]+getImports variant =+ [ "{-# LANGUAGE OverloadedStrings #-}"+ , ""+ , import_ "Prelude"+ , qualify "Prelude" "P"+ , import_ "Data.Monoid (mempty)"+ , ""+ , import_ h+ , qualify h "H"+ , import_ a+ , qualify a "A"+ ]+ where+ import_ = ("import " ++)+ qualify name short = "import qualified " ++ name ++ " as " ++ short+ h = getModuleName variant+ a = getAttributeModuleName variant++-- | Convert the HTML to blaze code.+--+blazeFromHtml :: HtmlVariant -- ^ Variant to use+ -> Bool -- ^ Produce standalone code+ -> Bool -- ^ Should we ignore errors+ -> String -- ^ Template name+ -> String -- ^ HTML code+ -> String -- ^ Resulting code+blazeFromHtml variant standalone ignore name =+ unlines . addSignature . fromHtml variant ignore+ . joinHtmlDoctype . minimizeBlocks+ . removeEmptyText . fst . makeTree variant ignore []+ . parseTagsOptions parseOptions { optTagPosition = True }+ where+ addSignature body = if standalone then [ name ++ " :: Html"+ , name ++ " = do"+ ] ++ indent body+ else body++-- | Indent block of code.+--+indent :: [String] -> [String]+indent = map (" " ++)++-- | Main function+--+main :: IO ()+main = do+ args <- getOpt Permute options <$> getArgs+ case args of+ (o, n, []) -> let v = getVariant o+ s = standalone' o+ i = ignore' o+ in do imports' v o+ main' v s i n+ (_, _, _) -> putStr help+ where+ -- No files given, work with stdin+ main' variant standalone ignore [] = interact $+ blazeFromHtml variant standalone ignore "template"++ -- Handle all files+ main' variant standalone ignore files = forM_ files $ \file -> do+ body <- readFile file+ putStrLn $ blazeFromHtml variant standalone ignore+ (dropExtension file) body++ -- Print imports if needed+ imports' variant opts = when (standalone' opts) $+ putStrLn $ unlines $ getImports variant++ -- Should we produce standalone code?+ standalone' opts = ArgStandalone `elem` opts++ -- Should we ignore errors?+ ignore' opts = ArgIgnoreErrors `elem` opts++ -- Get the variant from the options+ getVariant opts = fromMaybe defaultHtmlVariant $ listToMaybe $+ flip concatMap opts $ \o -> case o of (ArgHtmlVariant x) -> [x]+ _ -> []++-- | Help information.+--+help :: String+help = unlines $+ [ "This is a tool to convert HTML code to BlazeHtml code. It is still"+ , "experimental and the results might need to be edited manually."+ , ""+ , "USAGE"+ , ""+ , " blaze-from-html [OPTIONS...] [FILES ...]"+ , ""+ , "When no files are given, it works as a filter."+ , ""+ , "EXAMPLE"+ , ""+ , " blaze-from-html -v html4-strict index.html"+ , ""+ , "This converts the index.html file to Haskell code, writing to stdout."+ , ""+ , "OPTIONS"+ , usageInfo "" options+ , "VARIANTS"+ , ""+ ] +++ map ((" " ++) . fst) (M.toList htmlVariants) +++ [ ""+ , "By default, " ++ show defaultHtmlVariant ++ " is used."+ ]++-- | Options for the CLI program+--+data Arg = ArgHtmlVariant HtmlVariant+ | ArgStandalone+ | ArgIgnoreErrors+ deriving (Show, Eq)++-- | A description of the options+--+options :: [OptDescr Arg]+options =+ [ Option "v" ["html-variant"] htmlVariantOption "HTML variant to use"+ , Option "s" ["standalone"] (NoArg ArgStandalone) "Produce standalone code"+ , Option "e" ["ignore-errors"] (NoArg ArgIgnoreErrors) "Ignore most errors"+ ]+ where+ htmlVariantOption = flip ReqArg "VARIANT" $ \name -> ArgHtmlVariant $+ fromMaybe (error $ "No HTML variant called " ++ name ++ " found.")+ (M.lookup name htmlVariants)++-- | The default HTML variant+--+defaultHtmlVariant :: HtmlVariant+defaultHtmlVariant = html5
+ src/Util/GenerateHtmlCombinators.hs view
@@ -0,0 +1,470 @@+{-# LANGUAGE CPP #-}++#define DO_NOT_EDIT (doNotEdit __FILE__ __LINE__)++-- | Generates code for HTML tags.+--+module Util.GenerateHtmlCombinators where++import Control.Arrow ((&&&))+import Data.List (sort, sortBy, intersperse, intercalate)+import Data.Ord (comparing)+import System.Directory (createDirectoryIfMissing)+import System.FilePath ((</>), (<.>))+import Data.Map (Map)+import qualified Data.Map as M+import Data.Char (toLower)+import qualified Data.Set as S++import Util.Sanitize (sanitize, prelude)++-- | Datatype for an HTML variant.+--+data HtmlVariant = HtmlVariant+ { version :: [String]+ , docType :: [String]+ , parents :: [String]+ , leafs :: [String]+ , attributes :: [String]+ , selfClosing :: Bool+ } deriving (Eq)++instance Show HtmlVariant where+ show = map toLower . intercalate "-" . version++-- | Get the full module name for an HTML variant.+--+getModuleName :: HtmlVariant -> String+getModuleName = ("Text.Blaze." ++) . intercalate "." . version++-- | Get the attribute module name for an HTML variant.+--+getAttributeModuleName :: HtmlVariant -> String+getAttributeModuleName = (++ ".Attributes") . getModuleName++-- | Check if a given name causes a name clash.+--+isNameClash :: HtmlVariant -> String -> Bool+isNameClash v t+ -- Both an element and an attribute+ | (t `elem` parents v || t `elem` leafs v) && t `elem` attributes v = True+ -- Already a prelude function+ | sanitize t `S.member` prelude = True+ | otherwise = False++-- | Write an HTML variant.+--+writeHtmlVariant :: HtmlVariant -> IO ()+writeHtmlVariant htmlVariant = do+ -- Make a directory.+ createDirectoryIfMissing True basePath++ let tags = zip parents' (repeat makeParent)+ ++ zip leafs' (repeat (makeLeaf $ selfClosing htmlVariant))+ sortedTags = sortBy (comparing fst) tags+ appliedTags = map (\(x, f) -> f x) sortedTags++ -- Write the main module.+ writeFile' (basePath <.> "hs") $ removeTrailingNewlines $ unlines+ [ DO_NOT_EDIT+ , "{-# LANGUAGE OverloadedStrings #-}"+ , "-- | This module exports HTML combinators used to create documents."+ , "--"+ , exportList modulName $ "module Text.Blaze.Html"+ : "docType"+ : "docTypeHtml"+ : map (sanitize . fst) sortedTags+ , DO_NOT_EDIT+ , "import Prelude ((>>), (.))"+ , ""+ , "import Text.Blaze"+ , "import Text.Blaze.Internal"+ , "import Text.Blaze.Html"+ , ""+ , makeDocType $ docType htmlVariant+ , makeDocTypeHtml $ docType htmlVariant+ , unlines appliedTags+ ]++ let sortedAttributes = sort attributes'++ -- Write the attribute module.+ writeFile' (basePath </> "Attributes.hs") $ removeTrailingNewlines $ unlines+ [ DO_NOT_EDIT+ , "-- | This module exports combinators that provide you with the"+ , "-- ability to set attributes on HTML elements."+ , "--"+ , "{-# LANGUAGE OverloadedStrings #-}"+ , exportList attributeModuleName $ map sanitize sortedAttributes+ , DO_NOT_EDIT+ , "import Prelude ()"+ , ""+ , "import Text.Blaze.Internal (Attribute, AttributeValue, attribute)"+ , ""+ , unlines (map makeAttribute sortedAttributes)+ ]+ where+ basePath = "src" </> "Text" </> "Blaze" </> foldl1 (</>) version'+ modulName = getModuleName htmlVariant+ attributeModuleName = getAttributeModuleName htmlVariant+ attributes' = attributes htmlVariant+ parents' = parents htmlVariant+ leafs' = leafs htmlVariant+ version' = version htmlVariant+ removeTrailingNewlines = reverse . drop 2 . reverse+ writeFile' file content = do+ putStrLn ("Generating " ++ file)+ writeFile file content++-- | Create a string, consisting of @x@ spaces, where @x@ is the length of the+-- argument.+--+spaces :: String -> String+spaces = flip replicate ' ' . length++-- | Join blocks of code with a newline in between.+--+unblocks :: [String] -> String+unblocks = unlines . intersperse "\n"++-- | A warning to not edit the generated code.+--+doNotEdit :: FilePath -> Int -> String+doNotEdit fileName lineNumber = init $ unlines+ [ "-- WARNING: The next block of code was automatically generated by"+ , "-- " ++ fileName ++ ":" ++ show lineNumber+ , "--"+ ]++-- | Generate an export list for a Haskell module.+--+exportList :: String -- ^ Module name.+ -> [String] -- ^ List of functions.+ -> String -- ^ Resulting string.+exportList _ [] = error "exportList without functions."+exportList name (f:functions) = unlines $+ [ "module " ++ name+ , " ( " ++ f+ ] +++ map (" , " ++) functions +++ [ " ) where"]++-- | Generate a function for a doctype.+--+makeDocType :: [String] -> String+makeDocType lines' = unlines+ [ DO_NOT_EDIT+ , "-- | Combinator for the document type. This should be placed at the top"+ , "-- of every HTML page."+ , "--"+ , "-- Example:"+ , "--"+ , "-- > docType"+ , "--"+ , "-- Result:"+ , "--"+ , unlines (map ("-- > " ++) lines') ++ "--"+ , "docType :: Html -- ^ The document type HTML."+ , "docType = preEscapedText " ++ show (unlines lines')+ , "{-# INLINE docType #-}"+ ]++-- | Generate a function for the HTML tag (including the doctype).+--+makeDocTypeHtml :: [String] -- ^ The doctype.+ -> String -- ^ Resulting combinator function.+makeDocTypeHtml lines' = unlines+ [ DO_NOT_EDIT+ , "-- | Combinator for the @\\<html>@ element. This combinator will also"+ , "-- insert the correct doctype."+ , "--"+ , "-- Example:"+ , "--"+ , "-- > docTypeHtml $ span $ text \"foo\""+ , "--"+ , "-- Result:"+ , "--"+ , unlines (map ("-- > " ++) lines') ++ "-- > <html><span>foo</span></html>"+ , "--"+ , "docTypeHtml :: Html -- ^ Inner HTML."+ , " -> Html -- ^ Resulting HTML."+ , "docTypeHtml inner = docType >> html inner"+ , "{-# INLINE docTypeHtml #-}"+ ]++-- | Generate a function for an HTML tag that can be a parent.+--+makeParent :: String -> String+makeParent tag = unlines+ [ DO_NOT_EDIT+ , "-- | Combinator for the @\\<" ++ tag ++ ">@ element."+ , "--"+ , "-- Example:"+ , "--"+ , "-- > " ++ function ++ " $ span $ text \"foo\""+ , "--"+ , "-- Result:"+ , "--"+ , "-- > <" ++ tag ++ "><span>foo</span></" ++ tag ++ ">"+ , "--"+ , function ++ " :: Html -- ^ Inner HTML."+ , spaces function ++ " -> Html -- ^ Resulting HTML."+ , function ++ " = Parent \"" ++ tag ++ "\" \"<" ++ tag+ ++ "\" \"</" ++ tag ++ ">\"" ++ modifier+ , "{-# INLINE " ++ function ++ " #-}"+ ]+ where+ function = sanitize tag+ modifier = if tag `elem` ["style", "script"] then " . external" else ""++-- | Generate a function for an HTML tag that must be a leaf.+--+makeLeaf :: Bool -- ^ Make leaf tags self-closing+ -> String -- ^ Tag for the combinator+ -> String -- ^ Combinator code+makeLeaf closing tag = unlines+ [ DO_NOT_EDIT+ , "-- | Combinator for the @\\<" ++ tag ++ " />@ element."+ , "--"+ , "-- Example:"+ , "--"+ , "-- > " ++ function+ , "--"+ , "-- Result:"+ , "--"+ , "-- > <" ++ tag ++ " />"+ , "--"+ , function ++ " :: Html -- ^ Resulting HTML."+ , function ++ " = Leaf \"" ++ tag ++ "\" \"<" ++ tag ++ "\" " ++ "\""+ ++ (if closing then " /" else "") ++ ">\""+ , "{-# INLINE " ++ function ++ " #-}"+ ]+ where+ function = sanitize tag++-- | Generate a function for an HTML attribute.+--+makeAttribute :: String -> String+makeAttribute name = unlines+ [ DO_NOT_EDIT+ , "-- | Combinator for the @" ++ name ++ "@ attribute."+ , "--"+ , "-- Example:"+ , "--"+ , "-- > div ! " ++ function ++ " \"bar\" $ \"Hello.\""+ , "--"+ , "-- Result:"+ , "--"+ , "-- > <div " ++ name ++ "=\"bar\">Hello.</div>"+ , "--"+ , function ++ " :: AttributeValue -- ^ Attribute value."+ , spaces function ++ " -> Attribute -- ^ Resulting attribute."+ , function ++ " = attribute \"" ++ name ++ "\" \" "+ ++ name ++ "=\\\"\""+ , "{-# INLINE " ++ function ++ " #-}"+ ]+ where+ function = sanitize name++-- | HTML 4.01 Strict.+-- A good reference can be found here: http://www.w3schools.com/tags/default.asp+--+html4Strict :: HtmlVariant+html4Strict = HtmlVariant+ { version = ["Html4", "Strict"]+ , docType =+ [ "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\""+ , " \"http://www.w3.org/TR/html4/strict.dtd\">"+ ]+ , parents =+ [ "a", "abbr", "acronym", "address", "b", "bdo", "big", "blockquote"+ , "body" , "button", "caption", "cite", "code", "colgroup", "dd", "del"+ , "dfn", "div" , "dl", "dt", "em", "fieldset", "form", "h1", "h2", "h3"+ , "h4", "h5", "h6", "head", "html", "i", "ins" , "kbd", "label"+ , "legend", "li", "map", "noscript", "object", "ol", "optgroup"+ , "option", "p", "pre", "q", "samp", "script", "select", "small"+ , "span", "strong", "style", "sub", "sup", "table", "tbody", "td"+ , "textarea", "tfoot", "th", "thead", "title", "tr", "tt", "ul", "var"+ ]+ , leafs =+ [ "area", "br", "col", "hr", "link", "img", "input", "meta", "param"+ ]+ , attributes =+ [ "abbr", "accept", "accesskey", "action", "align", "alt", "archive"+ , "axis", "border", "cellpadding", "cellspacing", "char", "charoff"+ , "charset", "checked", "cite", "class", "classid", "codebase"+ , "codetype", "cols", "colspan", "content", "coords", "data", "datetime"+ , "declare", "defer", "dir", "disabled", "enctype", "for", "frame"+ , "headers", "height", "href", "hreflang", "http-equiv", "id", "label"+ , "lang", "maxlength", "media", "method", "multiple", "name", "nohref"+ , "onabort", "onblur", "onchange", "onclick", "ondblclick", "onfocus"+ , "onkeydown", "onkeypress", "onkeyup", "onload", "onmousedown"+ , "onmousemove", "onmouseout", "onmouseover", "onmouseup", "onreset"+ , "onselect", "onsubmit", "onunload", "profile", "readonly", "rel"+ , "rev", "rows", "rowspan", "rules", "scheme", "scope", "selected"+ , "shape", "size", "span", "src", "standby", "style", "summary"+ , "tabindex", "title", "type", "usemap", "valign", "value", "valuetype"+ , "width"+ ]+ , selfClosing = False+ }++-- | HTML 4.0 Transitional+--+html4Transitional :: HtmlVariant+html4Transitional = HtmlVariant+ { version = ["Html4", "Transitional"]+ , docType =+ [ "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\""+ , " \"http://www.w3.org/TR/html4/loose.dtd\">"+ ]+ , parents = parents html4Strict +++ [ "applet", "center", "dir", "font", "iframe", "isindex", "menu"+ , "noframes", "s", "u"+ ]+ , leafs = leafs html4Strict ++ ["basefont"]+ , attributes = attributes html4Strict +++ [ "background", "bgcolor", "clear", "compact", "hspace", "language"+ , "noshade", "nowrap", "start", "target", "vspace"+ ]+ , selfClosing = False+ }++-- | HTML 4.0 FrameSet+--+html4FrameSet :: HtmlVariant+html4FrameSet = HtmlVariant+ { version = ["Html4", "FrameSet"]+ , docType =+ [ "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 FrameSet//EN\""+ , " \"http://www.w3.org/TR/html4/frameset.dtd\">"+ ]+ , parents = parents html4Transitional ++ ["frameset"]+ , leafs = leafs html4Transitional ++ ["frame"]+ , attributes = attributes html4Transitional +++ [ "frameborder", "scrolling"+ ]+ , selfClosing = False+ }++-- | XHTML 1.0 Strict+--+xhtml1Strict :: HtmlVariant+xhtml1Strict = HtmlVariant+ { version = ["XHtml1", "Strict"]+ , docType =+ [ "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\""+ , " \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">"+ ]+ , parents = parents html4Strict+ , leafs = leafs html4Strict+ , attributes = attributes html4Strict+ , selfClosing = True+ }++-- | XHTML 1.0 Transitional+--+xhtml1Transitional :: HtmlVariant+xhtml1Transitional = HtmlVariant+ { version = ["XHtml1", "Transitional"]+ , docType =+ [ "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\""+ , " \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">"+ ]+ , parents = parents html4Transitional+ , leafs = leafs html4Transitional+ , attributes = attributes html4Transitional+ , selfClosing = True+ }++-- | XHTML 1.0 FrameSet+--+xhtml1FrameSet :: HtmlVariant+xhtml1FrameSet = HtmlVariant+ { version = ["XHtml1", "FrameSet"]+ , docType =+ [ "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 FrameSet//EN\""+ , " \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd\">"+ ]+ , parents = parents html4FrameSet+ , leafs = leafs html4FrameSet+ , attributes = attributes html4FrameSet+ , selfClosing = True+ }++-- | HTML 5.0+-- A good reference can be found here:+-- http://www.w3schools.com/html5/html5_reference.asp+--+html5 :: HtmlVariant+html5 = HtmlVariant+ { version = ["Html5"]+ , docType = ["<!DOCTYPE HTML>"]+ , parents =+ [ "a", "abbr", "address", "article", "aside", "audio", "b", "base"+ , "bdo", "blockquote", "body", "button", "canvas", "caption", "cite"+ , "code", "colgroup", "command", "datalist", "dd", "del", "details"+ , "dfn", "div", "dl", "dt", "em", "fieldset", "figcaption", "figure"+ , "footer", "form", "h1", "h2", "h3", "h4", "h5", "h6", "head", "header"+ , "hgroup", "html", "i", "iframe", "ins", "keygen", "kbd", "label"+ , "legend", "li", "map", "mark", "menu", "meter", "nav", "noscript"+ , "object", "ol", "optgroup", "option", "output", "p", "pre", "progress"+ , "q", "rp", "rt", "ruby", "samp", "script", "section", "select"+ , "small", "source", "span", "strong", "style", "sub", "summary", "sup"+ , "table", "tbody", "td", "textarea", "tfoot", "th", "thead", "time"+ , "title", "tr", "ul", "var", "video"+ ]+ , leafs =+ [ "area", "br", "col", "embed", "hr", "img", "input", "meta", "link"+ , "param"+ ]+ , attributes =+ [ "accept", "accept-charset", "accesskey", "action", "alt", "async"+ , "autocomplete", "autofocus", "autoplay", "challenge", "charset"+ , "checked", "cite", "class", "cols", "colspan", "content"+ , "contenteditable", "contextmenu", "controls", "coords", "data"+ , "datetime", "defer", "dir", "disabled", "draggable", "enctype", "for"+ , "form", "formaction", "formenctype", "formmethod", "formnovalidate"+ , "formtarget", "headers", "height", "hidden", "high", "href"+ , "hreflang", "http-equiv", "icon", "id", "ismap", "item", "itemprop"+ , "keytype", "label", "lang", "list", "loop", "low", "manifest", "max"+ , "maxlength", "media", "method", "min", "multiple", "name"+ , "novalidate", "onbeforeonload", "onbeforeprint", "onblur", "oncanplay"+ , "oncanplaythrough", "onchange", "oncontextmenu", "onclick"+ , "ondblclick", "ondrag", "ondragend", "ondragenter", "ondragleave"+ , "ondragover", "ondragstart", "ondrop", "ondurationchange", "onemptied"+ , "onended", "onerror", "onfocus", "onformchange", "onforminput"+ , "onhaschange", "oninput", "oninvalid", "onkeydown", "onkeyup"+ , "onload", "onloadeddata", "onloadedmetadata", "onloadstart"+ , "onmessage", "onmousedown", "onmousemove", "onmouseout", "onmouseover"+ , "onmouseup", "onmousewheel", "ononline", "onpagehide", "onpageshow"+ , "onpause", "onplay", "onplaying", "onprogress", "onpropstate"+ , "onratechange", "onreadystatechange", "onredo", "onresize", "onscroll"+ , "onseeked", "onseeking", "onselect", "onstalled", "onstorage"+ , "onsubmit", "onsuspend", "ontimeupdate", "onundo", "onunload"+ , "onvolumechange", "onwaiting", "open", "optimum", "pattern", "ping"+ , "placeholder", "preload", "pubdate", "radiogroup", "readonly", "rel"+ , "required", "reversed", "rows", "rowspan", "sandbox", "scope"+ , "scoped", "seamless", "selected", "shape", "size", "sizes", "span"+ , "spellcheck", "src", "srcdoc", "start", "step", "style", "subject"+ , "summary", "tabindex", "target", "title", "type", "usemap", "value"+ , "width", "wrap", "xmlns"+ ]+ , selfClosing = False+ }++-- | A map of HTML variants, per version, lowercase.+--+htmlVariants :: Map String HtmlVariant+htmlVariants = M.fromList $ map (show &&& id)+ [ html4Strict+ , html4Transitional+ , html4FrameSet+ , xhtml1Strict+ , xhtml1Transitional+ , xhtml1FrameSet+ , html5+ ]++main :: IO ()+main = mapM_ (writeHtmlVariant . snd) $ M.toList htmlVariants
+ src/Util/Sanitize.hs view
@@ -0,0 +1,80 @@+-- | A program to sanitize an HTML tag to a Haskell function.+--+module Util.Sanitize+ ( sanitize+ , keywords+ , prelude+ ) where++import Data.Char (toLower, toUpper)+import Data.Set (Set)+import qualified Data.Set as S++-- | Sanitize a tag. This function returns a name that can be used as+-- combinator in haskell source code.+--+-- Examples:+--+-- > sanitize "class" == "class_"+-- > sanitize "http-equiv" == "httpEquiv"+--+sanitize :: String -> String+sanitize str+ | lower == "doctypehtml" = "docTypeHtml"+ | otherwise = appendUnderscore $ removeDash lower+ where+ lower = map toLower str++ -- Remove a dash, replacing it by camelcase notation+ --+ -- Example:+ --+ -- > removeDash "foo-bar" == "fooBar"+ --+ removeDash ('-' : x : xs) = toUpper x : removeDash xs+ removeDash (x : xs) = x : removeDash xs+ removeDash [] = []++ appendUnderscore t | t `S.member` keywords = t ++ "_"+ | otherwise = t++-- | A set of standard Haskell keywords, which cannot be used as combinators.+--+keywords :: Set String+keywords = S.fromList+ [ "case", "class", "data", "default", "deriving", "do", "else", "if"+ , "import", "in", "infix", "infixl", "infixr", "instance" , "let", "module"+ , "newtype", "of", "then", "type", "where"+ ]++-- | Set of functions from the Prelude, which we do not use as combinators.+--+prelude :: Set String+prelude = S.fromList+ [ "abs", "acos", "acosh", "all", "and", "any", "appendFile", "asTypeOf"+ , "asin", "asinh", "atan", "atan2", "atanh", "break", "catch", "ceiling"+ , "compare", "concat", "concatMap", "const", "cos", "cosh", "curry", "cycle"+ , "decodeFloat", "div", "divMod", "drop", "dropWhile", "either", "elem"+ , "encodeFloat", "enumFrom", "enumFromThen", "enumFromThenTo", "enumFromTo"+ , "error", "even", "exp", "exponent", "fail", "filter", "flip"+ , "floatDigits", "floatRadix", "floatRange", "floor", "fmap", "foldl"+ , "foldl1", "foldr", "foldr1", "fromEnum", "fromInteger", "fromIntegral"+ , "fromRational", "fst", "gcd", "getChar", "getContents", "getLine", "head"+ , "id", "init", "interact", "ioError", "isDenormalized", "isIEEE"+ , "isInfinite", "isNaN", "isNegativeZero", "iterate", "last", "lcm"+ , "length", "lex", "lines", "log", "logBase", "lookup", "map", "mapM"+ , "mapM_", "max", "maxBound", "maximum", "maybe", "min", "minBound"+ , "minimum", "mod", "negate", "not", "notElem", "null", "odd", "or"+ , "otherwise", "pi", "pred", "print", "product", "properFraction", "putChar"+ , "putStr", "putStrLn", "quot", "quotRem", "read", "readFile", "readIO"+ , "readList", "readLn", "readParen", "reads", "readsPrec", "realToFrac"+ , "recip", "rem", "repeat", "replicate", "return", "reverse", "round"+ , "scaleFloat", "scanl", "scanl1", "scanr", "scanr1", "seq", "sequence"+ , "sequence_", "show", "showChar", "showList", "showParen", "showString"+ , "shows", "showsPrec", "significand", "signum", "sin", "sinh", "snd"+ , "span", "splitAt", "sqrt", "subtract", "succ", "sum", "tail", "take"+ , "takeWhile", "tan", "tanh", "toEnum", "toInteger", "toRational"+ , "truncate", "uncurry", "undefined", "unlines", "until", "unwords", "unzip"+ , "unzip3", "userError", "words", "writeFile", "zip", "zip3", "zipWith"+ , "zipWith3"+ ]