[finally a working XHTML Niklas Broberg **20080506195811] { hunk ./hsx-xhtml.cabal 20 -Exposed-Modules: HSX.Layout +Exposed-Modules: HSX.Layout, HSX.XHTML hunk ./hsx-xhtml.cabal 30 - FlexibleContexts + FlexibleContexts, + RankNTypes, + PatternSignatures, + ScopedTypeVariables addfile ./src/HSX/XHTML.hs hunk ./src/HSX/XHTML.hs 1 +module HSX.XHTML where + +import HSX.XMLGenerator + +import Data.List (intersperse) + +------------------------------------------ +-- We need separate classes for each separate +-- type that we want to parametrize over. + +class (XMLGenerator m, EmbedAsChild m t, EmbedAsAttr m (Attr String t)) => StringType m t +class (XMLGenerator m, EmbedAsAttr m (Attr String t)) => IntType m t +class (XMLGenerator m, EmbedAsAttr m (Attr String t)) => BoolType m t +class (XMLGenerator m, EmbedAsAttr m (Attr String t)) => ShapeType m t +class (XMLGenerator m, EmbedAsAttr m (Attr String t)) => DirType m t +class (XMLGenerator m, EmbedAsChild m t ) => LegendType m t +class (XMLGenerator m, EmbedAsAttr m t ) => InputType m t +class (XMLGenerator m, EmbedAsAttr m t ) => CheckedType m t +class (XMLGenerator m, EmbedAsAttr m t ) => HTTPEquivType m t +class (XMLGenerator m, EmbedAsAttr m t ) => MetaNameType m t +class (XMLGenerator m, EmbedAsChild m t, EmbedAsAttr m t ) => ScriptType m t +class (XMLGenerator m, EmbedAsChild m t, EmbedAsAttr m t ) => OptionType m t +class (XMLGenerator m, EmbedAsChild m t ) => CaptionType m t + +-- We already get the appropriate EmbedAsChild and EmbedAsAttr instances +-- from XMLGenerator, which has them as super-classes for String, Int and Bool. +instance XMLGenerator m => StringType m String +instance XMLGenerator m => IntType m Int +instance XMLGenerator m => BoolType m Bool + +----------------------------------------- + +page ::(StringType m title, + EmbedAsAttr m a, + EmbedAsChild m body) + => title -> [a] -> body -> GenXML m +page title attrs html + = + + <% title %> + + <% html %> + + + +-- 'a' + +link, anchor :: (StringType m s, EmbedAsChild m c) => s -> c -> GenXML m +link url conts = <% conts %> +anchor name conts = <% conts %> + +-- 'abbr' and 'acronym' + + +abbr, acronym :: (StringType m s1, StringType m s2) + => s1 -> s2 -> GenXML m +abbr full a = <% a %> +acronym full a = <% a %> + +-- 'address' + +address :: (XMLGenerator m, EmbedAsChild m l) => [l] -> GenXML m +address ls =
<% intersperse <% br %> $ map asChild ls %>
+ +-- 'area' +area :: (StringType m alt, StringType m url, ShapeType m sh) + => alt -> sh -> url -> GenXML m +area altTxt sh url = + altTxt + +{- +-- The tags + +-- TODO: check if these may contain other tags +-- TODO: these should be simulated with style! + +--tt, i, b, big, small, rtl :: IsXMLs a => a -> HSP XML +tt, i, b, big, small :: EmbedAsChild m a => a -> GenXML m +tt a = <% a %> +i a = <% a %> +b a = <% a %> +big a = <% a %> +small a = <% a %> +-} + +bdo :: (EmbedAsChild m a, DirType m d) => d -> a -> GenXML m +bdo dir a = <% a %> + +-- 'base' +base :: (StringType m s) => s -> GenXML m +base url = + +-- 'blockquote' and 'q' + +blockquote, q :: EmbedAsChild m a => a -> GenXML m +blockquote a =
<% a %>
+q a = <% a %> + +-- 'br' +br :: XMLGenerator m => GenXML m +br =
+ +-- 'button' +-- perhaps there should be more to this? + +button :: EmbedAsChild m a => a -> GenXML m +button a = + +{- + +-- The tags +-- These should be simulated with style! + +em, strong, dfn, code, samp, kbd, var, cite :: +-} + +-- 'del' and 'ins' + +del, ins :: (StringType m s) => s -> GenXML m +del str = <% str %> +ins str = <% str %> + +-- 'div', 'span' and 'p' + +div, span, p :: EmbedAsChild m a => a -> GenXML m +div a =
<% a %>
+span a = <% a %> +p a =

<% a %>

+ +-- Definition lists: 'dl', 'dt' and 'dd' + +dl :: (EmbedAsChild m a, EmbedAsChild m b) => [(a,[b])] -> GenXML m +dl entries =
<% concatMap mkDef entries %>
+ where mkDef (a, bs) =
<% a %>
: map (\b ->
<% b %>
) bs + +-- 'fieldset' and 'legend' + +--fieldset :: IsXMLs c => Legend -> c -> HSP XML +fieldset :: (LegendType m l, EmbedAsChild m a) => l -> a -> GenXML m +fieldset lgd conts = +
+ <% lgd %> + <% conts %> +
+ +-- 'form' + +form :: (StringType m s, EmbedAsChild m a) => s -> a -> GenXML m +form url conts =
<% conts %>
+ +-- 'head' and 'title' + +head :: (StringType m s, EmbedAsChild m a) => s -> a -> GenXML m +head ttl conts = + <% ttl %><% conts %> + + + +-- 'h1' - 'h6' + +-- This one would do well with some way of defining +-- tag names from expressions! +h :: EmbedAsChild m a => Int -> a -> GenXML m +h n a = genElement (Nothing, ("h" ++ show n)) [] [asChild a] + +h1,h2,h3,h4,h5,h6 :: EmbedAsChild m a => a -> GenXML m +h1 = h 1 +h2 = h 2 +h3 = h 3 +h4 = h 4 +h5 = h 5 +h6 = h 6 + +-- 'hr' + +hr :: XMLGenerator m => GenXML m +hr =
+ +-- 'img' and 'map' + +img :: (StringType m alt, StringType m url) => alt -> url -> GenXML m +img altTxt url = altTxt + +imgmap :: (StringType m alt, StringType m url, StringType m map, + StringType m aalt, StringType m aurl, ShapeType m sh) + => alt -> url -> (map, [(aalt, sh, aurl)]) -> GenXMLList m +imgmap altTxt url (im,areas) = + do ix <- altTxt + mp <- <% map (\(a,s,u) -> area a s u) areas %> + return $ [ix,mp] + + +-- 'input' and 'label' + +label :: (StringType m s, EmbedAsChild m a) => s -> a -> GenXML m +label inp a = + + +--input :: InputType -> String -> HSP XML +input :: (InputType m i, StringType m s) => i -> s -> GenXML m +input itype n = + +data Input c s + = Checkbox c + | File + | Image s s + | Hidden + | Button s + | Password + | Text + | Radio c + | Submit s + | Reset s + +type CInput c = Input c String +type SInput s = Input Checked s + +data Checked = Checked | Unchecked + +instance XMLGenerator m => EmbedAsAttr m Checked where + asAttr Checked = asAttr ("checked" := "checked") + asAttr Unchecked = return [] + +instance XMLGenerator m => CheckedType m Checked + +instance (XMLGenerator m, CheckedType m c, StringType m s) => EmbedAsAttr m (Input c s) where + asAttr inp = do + t <- asAttr $ typeOf inp + es <- extras inp + return $ t ++ es + where typeOf i = let t = case i of + Checkbox _ -> "checkbox" + File -> "file" + Image _ _ -> "image" + Hidden -> "hidden" + Button _ -> "button" + Password -> "password" + Text -> "text" + Radio _ -> "radio" + Submit _ -> "submit" + Reset _ -> "reset" + in "type" := t + extras i = case i of + Checkbox b -> asAttr b + Radio b -> asAttr b + Image altText url -> asAttr ["alt" := altText, "src" := url] + Button val -> asAttr ["value" := val] + Submit val -> asAttr ["value" := val] + Reset val -> asAttr ["value" := val] + _ -> return [] + +instance (XMLGenerator m, CheckedType m c, StringType m s) => InputType m (Input c s) + +submitButton, resetButton :: forall m n v . (StringType m n, StringType m v) => n -> v -> GenXML m +submitButton n val = input (Submit val :: SInput v) n +resetButton n val = input (Reset val :: SInput v) n + + +--checkbox :: Bool -> String -> String -> HSP [XML] + +checkbox :: forall m c s1 s2 . (CheckedType m c, StringType m s1, StringType m s2) => c -> s1 -> s2 -> GenXMLList m +checkbox check n lbl = + sequence $ [input (Checkbox check :: CInput c) n, label lbl n] + +-- TODO: Should be more fancy stuff in here + +-- 'meta' + +data HTTPEquiv + = ContentType + | Expires + | Refresh + | SetCookie + +instance XMLGenerator m => EmbedAsAttr m HTTPEquiv where + asAttr he = asAttr ("http-equiv" := toStr he) + where toStr ContentType = "content-type" + toStr Expires = "expires" + toStr Refresh = "refresh" + toStr SetCookie = "set-cookie" + +instance XMLGenerator m => HTTPEquivType m HTTPEquiv + +data MetaName + = Author + | Description + | Keywords + | Generator + | Revised + | Others String + +instance XMLGenerator m => EmbedAsAttr m MetaName where + asAttr mn = asAttr ("name" := toStr mn) + where toStr Author = "author" + toStr Description = "description" + toStr Keywords = "keywords" + toStr Generator = "generator" + toStr Revised = "revised" + toStr (Others s) = s + +instance XMLGenerator m => MetaNameType m MetaName + +httpEquiv :: (HTTPEquivType m h, StringType m s) => h -> s -> GenXML m +httpEquiv equiv conts = + +meta :: (MetaNameType m mn, StringType m s) => mn -> s -> GenXML m +meta mn conts = + +-- 'object' and 'param' + +-- This one is rather difficult to standardise, I'll leave that to +-- libs to fix. + +--object :: IsXMLs a => [(String,String)] -> a -> HSP XML +object :: (EmbedAsChild m a, StringType m s1, StringType m s2) + => [(s1, s2)] -> a -> GenXML m +object pars alt = <% alt %><% mapM (uncurry param) pars %> + +param :: (StringType m s1, StringType m s2) => s1 -> s2 -> GenXML m +param n v = + + +-- 'ol', 'ul' and 'li' + +ol, orderedList, ul, unorderedList :: (EmbedAsChild m a) => [a] -> GenXML m +ol items =
    <% mapM listItem items %>
+orderedList = ol +ul items =
    <% mapM listItem items %>
+unorderedList = ul + +listItem :: EmbedAsChild m a => a -> GenXML m +listItem a =
  • <% a %>
  • + +-- 'pre' + +pre :: EmbedAsChild m a => a -> GenXML m +pre a =
    <% a %>
    + +-- 'script' and 'noscript' + +--script :: IsXMLs a => Script -> a -> [HSP XML] +script :: (ScriptType m s, EmbedAsChild m a) => s -> a -> GenXMLList m +script sc alt = sequence [, ] + +-- TODO: import HSP.JavaScript + +data JavaScript s = JavaScript s + +instance XMLGenerator m => EmbedAsAttr m (JavaScript s) where + asAttr _ = asAttr ("type" := "text/javascript") + +instance EmbedAsChild m s => EmbedAsChild m (JavaScript s) where + asChild (JavaScript s) = asChild s + +instance (XMLGenerator m, EmbedAsChild m s) => ScriptType m (JavaScript s) + + +-- 'select', 'option' and 'optgroup' + +data Option s + = Selected s s + | Unselected s s + +mkOption :: StringType m s => s -> s -> Bool -> Option s +mkOption conts val sel = case sel of + True -> Selected conts val + _ -> Unselected conts val + +instance StringType m s => EmbedAsAttr m (Option s) where + asAttr (Selected v _) = asAttr ("selected":="selected") >>= + \ss -> asAttr ("value":=v) >>= + \vs -> return $ ss ++ vs + asAttr (Unselected v _) = asAttr ("value":=v) + + + + +instance StringType m s => EmbedAsChild m (Option s) where + asChild (Selected _ c) = asChild c + asChild (Unselected _ c) = asChild c + +instance StringType m s => OptionType m (Option s) + +select' :: OptionType m o => [GenAttributeList m] -> [o] -> GenXML m +select' attrs opts = + +select :: forall m o . OptionType m o => [o] -> GenXML m +select = select' [return []] + +multiSelect :: forall m o . OptionType m o => [o] -> GenXML m +multiSelect = select' [asAttr $ "multiple" := "multiple"] + +sizedSelect :: (IntType m i, OptionType m o) => i -> [o] -> GenXML m +sizedSelect n = select' [asAttr $ "size" := n] + +sizedMultiSelect :: (IntType m i, OptionType m o) => i -> [o] -> GenXML m +sizedMultiSelect n = select' [asAttr $ "multiple" := "multiple", asAttr $ "size" := n] + +option :: OptionType m o => o -> GenXML m +option opt = + +type OptGroup s o = (s, [o]) + +groupSelect :: (StringType m s, OptionType m o) => [OptGroup s o] -> GenXML m +groupSelect gs = + +optgroup :: (StringType m s, OptionType m o) => s -> [o] -> GenXML m +optgroup s opts = <% mapM option opts %> + +-- 'style' + +-- I'll leave this for later, need to implement styles first. + +-- 'sub' and 'sup' + +sub, sup :: EmbedAsChild m a => a -> GenXML m +sub a = <% a %> +sup a = <% a %> + +-- 'table', 'tr', 'td', 'th' and 'caption', +-- 'colgroup' and 'col', +-- 'thead', 'tfoot' and 'tbody' + + +data Caption a + = Caption a + | NoCaption + +capt :: a -> Caption a +capt = Caption +noCapt :: Caption a +noCapt = NoCaption + +instance EmbedAsChild m c => EmbedAsChild m (Caption c) where + asChild NoCaption = return [] + asChild (Caption a) = asChild $ caption a + +instance (XMLGenerator m, EmbedAsChild m c) => CaptionType m (Caption c) + +caption :: EmbedAsChild m c => c -> GenXML m +caption a = <% a %> + + +colgroup :: (EmbedAsAttr m a, IntType m i) => i -> [[a]] -> GenXML m +colgroup spancols colattrs = + <% mapM col colattrs %> + +col :: EmbedAsAttr m a => [a] -> GenXML m +col ats = + +table :: (CaptionType m cap, EmbedAsChild m c) => cap -> [[c]] -> GenXML m +table cap rows = <% cap %><% mapM tableRow rows %>
    + +tableRow :: EmbedAsChild m a => [a] -> GenXML m +tableRow cells = <% mapM tableCell cells %> + +tableCell :: EmbedAsChild m a => a -> GenXML m +tableCell a = <% a %> + +headFootTable :: (EmbedAsChild m h, EmbedAsChild m b, EmbedAsChild m f) => h -> [[b]] -> f -> GenXML m +headFootTable hs body fs = + + <% hs %> + <% fs %> + <% mapM tableRow body %> +
    }