------------------------------------------------------------------------------
--- Library for HTML and CGI programming.
--- [This paper](http://www.informatik.uni-kiel.de/~mh/papers/PADL01.html)
--- contains a description of the basic ideas behind this library.
---
--- The installation of a cgi script written with this library
--- can be done by the command
---
--- curry makecgi -m initialForm -o /home/joe/public_html/prog.cgi prog
---
--- where `prog` is the name of the Curry program with
--- the cgi script, `/home/joe/public_html/prog.cgi` is
--- the desired location of the
--- compiled cgi script, and `initialForm` is the Curry expression
--- (of type IO HtmlForm) computing the HTML form (where `curry`
--- is the shell command calling the Curry system PAKCS or KiCS2).
---
--- @author Michael Hanus (with extensions by Bernd Brassel and Marco Comini)
--- @version December 2018
------------------------------------------------------------------------------
{-# OPTIONS_CYMAKE -Wno-incomplete-patterns #-}
module HTML(HtmlExp(..),HtmlPage(..),PageParam(..),
HtmlForm(..),FormParam(..),CookieParam(..),
CgiRef(..),idOfCgiRef,CgiEnv,HtmlHandler,
defaultEncoding,
form,standardForm,answerText,answerEncText,
cookieForm,getCookies,
page,standardPage,
pageEnc,pageCSS,pageMetaInfo,pageLinkInfo,pageBodyAttr,addPageParam,
formEnc,formCSS,formMetaInfo,formBodyAttr,addFormParam,
htxt,htxts,hempty,nbsp,h1,h2,h3,h4,h5,
par,section,header,footer,emphasize,strong,bold,italic,nav,code,
center,blink,teletype,pre,verbatim,address,href,anchor,
ulist,olist,litem,dlist,table,headedTable,addHeadings,
hrule,breakline,image,
styleSheet,style,textstyle,blockstyle,inline,block,
redirect,expires,
button,resetbutton,imageButton,coordinates,
textfield,password,textarea,checkbox,checkedbox,
radio_main,radio_main_off,radio_other,
selection,selectionInitial,multipleSelection,
hiddenfield,htmlQuote,htmlIsoUmlauts,addAttr,addAttrs,addClass,
showHtmlExps,showHtmlExp,showHtmlPage,
runFormServerWithKey,runFormServerWithKeyAndFormParams,
intForm,intFormMain,
getUrlParameter,urlencoded2string,string2urlencoded,
showLatexExps,showLatexExp,showLatexDoc,showLatexDocs,
showLatexDocsWithPackages,showLatexDocWithPackages,
germanLatexDoc,htmlSpecialChars2tex,
addSound,addCookies) where
import Char
import Directory (getHomeDirectory)
import Distribution (installDir)
import HtmlCgi
import IO
import List
import ReadNumeric (readNat, readHex)
import ReadShowTerm (showQTerm, readsQTerm)
import System
import Time
--import Unsafe(showAnyQExpression) -- to show status of cgi server
import Json
import Network.NamedSocket
import System.Random (getRandomSeed, nextInt)
infixl 0 `addAttr`
infixl 0 `addAttrs`
infixl 0 `addClass`
infixl 0 `addPageParam`
infixl 0 `addFormParam`
------------------------------------------------------------------------------
--- The default encoding used in generated web pages.
defaultEncoding :: String
defaultEncoding = "utf-8" --"iso-8859-1"
------------------------------------------------------------------------------
--- The (abstract) data type for representing references to input elements
--- in HTML forms.
data CgiRef = CgiRef String
deriving Eq
--- Internal identifier of a CgiRef (intended only for internal use in other
--- libraries!).
idOfCgiRef :: CgiRef -> String
idOfCgiRef (CgiRef i) = i
--- The type for representing cgi environments
--- (i.e., mappings from cgi references to the corresponding values of
--- the input elements).
type CgiEnv = CgiRef -> String
--- The type of event handlers in HTML forms.
type HtmlHandler = CgiEnv -> IO HtmlForm
--- The data type for representing HTML expressions.
--- @cons HtmlText s - a text string without any further structure
--- @cons HtmlStruct t as hs - a structure with a tag, attributes, and
--- HTML expressions inside the structure
--- @cons HtmlCRef h ref - an input element (described by the first argument)
--- with a cgi reference
--- @cons HtmlEvent h hdlr - an input element (first arg) with an associated
--- event handler (tpyically, a submit button)
data HtmlExp =
HtmlText String
| HtmlStruct String [(String,String)] [HtmlExp]
| HtmlCRef HtmlExp CgiRef
| HtmlEvent HtmlExp HtmlHandler
| AjaxEvent String HtmlHandler
| AjaxEvent2 HtmlExp HtmlHandler String String
--- Extracts the textual contents of a list of HTML expressions.
---
--- For instance,
--- textOf [HtmlText "xy", HtmlStruct "a" [] [HtmlText "bc"]] == "xy bc"
textOf :: [HtmlExp] -> String
textOf = unwords . filter (not . null) . map textOfHtmlExp
where
textOfHtmlExp (HtmlText s) = s
textOfHtmlExp (HtmlStruct _ _ hs) = textOf hs
textOfHtmlExp (HtmlCRef hexp _) = textOf [hexp]
textOfHtmlExp (HtmlEvent hexp _) = textOf [hexp]
textOfHtmlExp (AjaxEvent _ _) = ""
textOfHtmlExp (AjaxEvent2 hexp _ _ _) = textOf [hexp]
------------------------------------------------------------------------------
--- The data type for representing HTML forms (active web pages)
--- and return values of HTML forms.
--- @cons HtmlForm t ps hs - an HTML form with title t, optional parameters
--- (e.g., cookies) ps, and contents hs
--- @cons HtmlAnswer t c - an answer in an arbitrary format where t
--- is the content type (e.g., "text/plain") and c is the contents
data HtmlForm =
HtmlForm String [FormParam] [HtmlExp]
| HtmlAnswer String String -- content type (e.g., "text/plain") / content
| AjaxAnswer Json [([(String,String)],[HtmlExp])]
--- The possible parameters of an HTML form.
--- The parameters of a cookie (FormCookie) are its name and value and
--- optional parameters (expiration date, domain, path (e.g., the path "/"
--- makes the cookie valid for all documents on the server), security) which
--- are collected in a list.
--- @cons FormCookie name value params - a cookie to be sent to the
--- client's browser
--- @cons FormCSS s - a URL for a CSS file for this form
--- @cons FormJScript s - a URL for a Javascript file for this form
--- @cons FormOnSubmit s - a JavaScript statement to be executed when the form
--- is submitted (i.e., <form ... onsubmit="s">)
--- @cons FormTarget s - a name of a target frame where the output of the
--- script should be represented (should only be used
--- for scripts running in a frame)
--- @cons FormEnc - the encoding scheme of this form
--- @cons FormMeta as - meta information (in form of attributes) for this form
--- @cons HeadInclude he - HTML expression to be included in form header
--- @cons MultipleHandlers - indicates that the event handlers of the form
--- can be multiply used (i.e., are not deleted if the form is submitted
--- so that they are still available when going back in the browser;
--- but then there is a higher risk that the web server process
--- might overflow with unused events); the default is a single use
--- of event handlers, i.e., one cannot use the back button in the
--- browser and submit the same form again (which is usually
--- a reasonable behavior to avoid double submissions of data).
--- @cons BodyAttr ps - optional attribute for the body element (more than
--- one occurrence is allowed)
data FormParam = FormCookie String String [CookieParam]
| FormCSS String
| FormJScript String
| FormOnSubmit String
| FormTarget String
| FormEnc String
| FormMeta [(String,String)]
| HeadInclude HtmlExp
| MultipleHandlers
| BodyAttr (String,String)
--- An encoding scheme for a HTML form.
formEnc :: String -> FormParam
formEnc enc = FormEnc enc
--- A URL for a CSS file for a HTML form.
formCSS :: String -> FormParam
formCSS css = FormCSS css
--- Meta information for a HTML form. The argument is a list of
--- attributes included in the `meta`-tag in the header for this form.
formMetaInfo :: [(String,String)] -> FormParam
formMetaInfo attrs = FormMeta attrs
--- Optional attribute for the body element of the HTML form.
--- More than one occurrence is allowed, i.e., all such attributes are
--- collected.
formBodyAttr :: (String,String) -> FormParam
formBodyAttr attr = BodyAttr attr
--- A cookie to be sent to the client's browser when a HTML form is
--- requested.
formCookie :: (String,String) -> FormParam
formCookie (n,v) = FormCookie n v []
--- The possible parameters of a cookie.
data CookieParam = CookieExpire ClockTime
| CookieDomain String
| CookiePath String
| CookieSecure
--- A basic HTML form for active web pages with the default encoding
--- and a default background.
--- @param title - the title of the form
--- @param hexps - the form's body (list of HTML expressions)
--- @return an HTML form
form :: String -> [HtmlExp] -> HtmlForm
form title hexps = HtmlForm title [] hexps
--- A standard HTML form for active web pages where the title is included
--- in the body as the first header.
--- @param title - the title of the form
--- @param hexps - the form's body (list of HTML expressions)
--- @return an HTML form with the title as the first header
standardForm :: String -> [HtmlExp] -> HtmlForm
standardForm title hexps = form title (h1 [htxt title] : hexps)
--- An HTML form with simple cookies.
--- The cookies are sent to the client's browser together with this form.
--- @param title - the title of the form
--- @param cookies - the cookies as a list of name/value pairs
--- @param hexps - the form's body (list of HTML expressions)
--- @return an HTML form
cookieForm :: String -> [(String,String)] -> [HtmlExp] -> HtmlForm
cookieForm t cs he = HtmlForm t (map (\(n,v)->FormCookie n v []) cs) he
--- Add simple cookie to HTML form.
--- The cookies are sent to the client's browser together with this form.
--- @param cs - the cookies as a list of name/value pairs
--- @param form - the form to add cookies to
--- @return a new HTML form
addCookies :: [(String,String)] -> HtmlForm -> HtmlForm
addCookies cs (HtmlForm t fas hs) =
HtmlForm t (map (\ (n,v) -> FormCookie n v []) cs ++ fas) hs
addCookies _ (HtmlAnswer _ _) =
error "addCookies: cannot add cookie to Html answer"
-- Shows the cookie in standard syntax:
formatCookie :: (String,String,[CookieParam]) -> String
formatCookie (name,value,params) =
"Set-Cookie: " ++ name ++ "=" ++ string2urlencoded value ++
concatMap (\p->"; "++formatCookieParam p) params
-- Formats a cookie parameter:
formatCookieParam :: CookieParam -> String
formatCookieParam (CookieExpire e) = "expires=" ++ toCookieDateString e
formatCookieParam (CookieDomain d) = "domain=" ++ d
formatCookieParam (CookiePath p) = "path=" ++ p
formatCookieParam CookieSecure = "secure"
-- Formats a clock time into a date string for cookies:
toCookieDateString :: ClockTime -> String
toCookieDateString time =
let (CalendarTime y mo d h mi s tz) = toUTCTime time
in (show d ++ "-" ++ shortMonths!!(mo-1) ++ "-" ++ show y ++ " " ++
toTimeString (CalendarTime y mo d h mi s tz) ++ " UTC")
where shortMonths = ["Jan","Feb","Mar","Apr","May","Jun",
"Jul","Aug","Sep","Oct","Nov","Dec"]
--- A textual result instead of an HTML form as a result for active web pages.
--- @param txt - the contents of the result page
--- @return an HTML answer form
answerText :: String -> HtmlForm
answerText = HtmlAnswer "text/plain"
--- A textual result instead of an HTML form as a result for active web pages
--- where the encoding is given as the first parameter.
--- @param enc - the encoding of the text(e.g., "utf-8" or "iso-8859-1")
--- @param txt - the contents of the result page
--- @return an HTML answer form
answerEncText :: String -> String -> HtmlForm
answerEncText enc = HtmlAnswer ("text/plain; charset="++enc)
--- Adds a parameter to an HTML form.
--- @param form - a form
--- @param param - a form's parameter
--- @return an HTML form
addFormParam :: HtmlForm -> FormParam -> HtmlForm
addFormParam (HtmlForm title params hexps) param =
HtmlForm title (param:params) hexps
addFormParam hexp@(HtmlAnswer _ _) _ = hexp
addFormParams :: HtmlForm -> [FormParam] -> HtmlForm
addFormParams hform [] = hform
addFormParams hform (fp:fps) = addFormParams (hform `addFormParam` fp) fps
--- Adds redirection to given HTML form.
--- @param secs - Number of seconds to wait before executing autromatic redirection
--- @param url - The URL whereto redirect to
--- @param form - The form to add the header information to
redirect :: Int -> String -> HtmlForm -> HtmlForm
redirect secs url hform =
hform `addFormParam`
HeadInclude (HtmlStruct "meta" [("http-equiv","refresh"),
("content",show secs++"; URL="++url)] [])
--- Adds expire time to given HTML form.
--- @param secs - Number of seconds before document expires
--- @param form - The form to add the header information to
expires :: Int -> HtmlForm -> HtmlForm
expires secs hform =
hform `addFormParam`
HeadInclude (HtmlStruct "meta" [("http-equiv","expires"),
("content",show secs)] [])
--- Adds sound to given HTML form. The functions adds two different declarations
--- for sound, one invented by Microsoft for the internet explorer, one introduced
--- for netscape. As neither is an official part of HTML, addsound might not work
--- on all systems and browsers. The greatest chance is by using sound files
--- in MID-format.
--- @param soundfile - Name of file containing the sound to be played
--- @param loop - Should sound go on infinitely? Use with care.
--- @param form - The form to add sound to
addSound :: String -> Bool -> HtmlForm -> HtmlForm
addSound soundfile loop (HtmlForm title params conts) =
HtmlForm title
(HeadInclude
(HtmlStruct "bgsound"
[("src",soundfile),
("loop",if loop then "infinite" else "1")] []):params)
(HtmlStruct "embed"
((if loop then [("loop","true")] else []) ++
[("src",soundfile),("autostart","true"), ("hidden","true"),
("height","0"), ("width","0")]) []:
conts)
addSound _ _ (HtmlAnswer _ _)
= error "HTML.addSound: unable to add sound to general HTML Answer"
------------------------------------------------------------------------------
--- The data type for representing HTML pages.
--- The constructor arguments are the title, the parameters, and
--- the contents (body) of the web page.
data HtmlPage = HtmlPage String [PageParam] [HtmlExp]
--- The possible parameters of an HTML page.
--- @cons PageEnc - the encoding scheme of this page
--- @cons PageCSS s - a URL for a CSS file for this page
--- @cons PageJScript s - a URL for a Javascript file for this page
--- @cons PageMeta as - meta information (in form of attributes) for this page
--- @cons PageLink as - link information (in form of attributes) for this page
--- @cons PageBodyAttr attr - optional attribute for the body element of the
--- page (more than one occurrence is allowed)
data PageParam = PageEnc String
| PageCSS String
| PageJScript String
| PageMeta [(String,String)]
| PageLink [(String,String)]
| PageBodyAttr (String,String)
--- An encoding scheme for a HTML page.
pageEnc :: String -> PageParam
pageEnc enc = PageEnc enc
--- A URL for a CSS file for a HTML page.
pageCSS :: String -> PageParam
pageCSS css = PageCSS css
--- Meta information for a HTML page. The argument is a list of
--- attributes included in the `meta`-tag in the header for this page.
pageMetaInfo :: [(String,String)] -> PageParam
pageMetaInfo attrs = PageMeta attrs
--- Link information for a HTML page. The argument is a list of
--- attributes included in the `link`-tag in the header for this page.
pageLinkInfo :: [(String,String)] -> PageParam
pageLinkInfo attrs = PageLink attrs
--- Optional attribute for the body element of the web page.
--- More than one occurrence is allowed, i.e., all such attributes are
--- collected.
pageBodyAttr :: (String,String) -> PageParam
pageBodyAttr attr = PageBodyAttr attr
--- A basic HTML web page with the default encoding.
--- @param title - the title of the page
--- @param hexps - the page's body (list of HTML expressions)
--- @return an HTML page
page :: String -> [HtmlExp] -> HtmlPage
page title hexps = HtmlPage title [PageEnc defaultEncoding] hexps
--- A standard HTML web page where the title is included
--- in the body as the first header.
--- @param title - the title of the page
--- @param hexps - the page's body (list of HTML expressions)
--- @return an HTML page with the title as the first header
standardPage :: String -> [HtmlExp] -> HtmlPage
standardPage title hexps = page title (h1 [htxt title] : hexps)
--- Adds a parameter to an HTML page.
--- @param form - a page
--- @param param - a page's parameter
--- @return an HTML page
addPageParam :: HtmlPage -> PageParam -> HtmlPage
addPageParam (HtmlPage title params hexps) param =
HtmlPage title (param:params) hexps
------------------------------------------------------------------------------
-- some useful abbreviations:
--- Basic text as HTML expression.
--- The text may contain special HTML chars (like <,>,&,")
--- which will be quoted so that they appear as in the parameter string.
htxt :: String -> HtmlExp
htxt s = HtmlText (htmlQuote s)
--- A list of strings represented as a list of HTML expressions.
--- The strings may contain special HTML chars that will be quoted.
htxts :: [String] -> [HtmlExp]
htxts = map htxt
--- An empty HTML expression.
hempty :: HtmlExp
hempty = HtmlText ""
--- Non breaking Space
nbsp :: HtmlExp
nbsp = HtmlText " "
--- Header 1
h1 :: [HtmlExp] -> HtmlExp
h1 hexps = HtmlStruct "h1" [] hexps
--- Header 2
h2 :: [HtmlExp] -> HtmlExp
h2 hexps = HtmlStruct "h2" [] hexps
--- Header 3
h3 :: [HtmlExp] -> HtmlExp
h3 hexps = HtmlStruct "h3" [] hexps
--- Header 4
h4 :: [HtmlExp] -> HtmlExp
h4 hexps = HtmlStruct "h4" [] hexps
--- Header 5
h5 :: [HtmlExp] -> HtmlExp
h5 hexps = HtmlStruct "h5" [] hexps
--- Paragraph
par :: [HtmlExp] -> HtmlExp
par hexps = HtmlStruct "p" [] hexps
--- Section
section :: [HtmlExp] -> HtmlExp
section hexps = HtmlStruct "section" [] hexps
--- Header
header :: [HtmlExp] -> HtmlExp
header hexps = HtmlStruct "header" [] hexps
--- Footer
footer :: [HtmlExp] -> HtmlExp
footer hexps = HtmlStruct "footer" [] hexps
--- Emphasize
emphasize :: [HtmlExp] -> HtmlExp
emphasize hexps = HtmlStruct "em" [] hexps
--- Strong (more emphasized) text.
strong :: [HtmlExp] -> HtmlExp
strong hexps = HtmlStruct "strong" [] hexps
--- Boldface
bold :: [HtmlExp] -> HtmlExp
bold hexps = HtmlStruct "b" [] hexps
--- Italic
italic :: [HtmlExp] -> HtmlExp
italic hexps = HtmlStruct "i" [] hexps
--- Navigation
nav :: [HtmlExp] -> HtmlExp
nav doc = HtmlStruct "nav" [] doc
--- Program code
code :: [HtmlExp] -> HtmlExp
code hexps = HtmlStruct "code" [] hexps
--- Centered text
center :: [HtmlExp] -> HtmlExp
center hexps = HtmlStruct "center" [] hexps
--- Blinking text
blink :: [HtmlExp] -> HtmlExp
blink hexps = HtmlStruct "blink" [] hexps
--- Teletype font
teletype :: [HtmlExp] -> HtmlExp
teletype hexps = HtmlStruct "tt" [] hexps
--- Unformatted input, i.e., keep spaces and line breaks and
--- don't quote special characters.
pre :: [HtmlExp] -> HtmlExp
pre hexps = HtmlStruct "pre" [] hexps
--- Verbatim (unformatted), special characters (<,>,&,")
--- are quoted.
verbatim :: String -> HtmlExp
verbatim s = HtmlStruct "pre" [] [HtmlText (htmlQuote s)]
--- Address
address :: [HtmlExp] -> HtmlExp
address hexps = HtmlStruct "address" [] hexps
--- Hypertext reference
href :: String -> [HtmlExp] -> HtmlExp
href ref hexps = HtmlStruct "a" [("href",ref)] hexps
--- An anchored text with a hypertext reference inside a document.
anchor :: String -> [HtmlExp] -> HtmlExp
anchor anc hexps = HtmlStruct "span" [("id",anc)] hexps
--- Unordered list
--- @param items - the list items where each item is a list of HTML expressions
ulist :: [[HtmlExp]] -> HtmlExp
ulist items = HtmlStruct "ul" [] (map litem items)
--- Ordered list
--- @param items - the list items where each item is a list of HTML expressions
olist :: [[HtmlExp]] -> HtmlExp
olist items = HtmlStruct "ol" [] (map litem items)
--- A single list item (usually not explicitly used)
litem :: [HtmlExp] -> HtmlExp
litem hexps = HtmlStruct "li" [] hexps
--- Description list
--- @param items - a list of (title/description) pairs (of HTML expressions)
dlist :: [([HtmlExp],[HtmlExp])] -> HtmlExp
dlist items = HtmlStruct "dl" [] (concatMap ditem items)
where
ditem (hexps1,hexps2) = [HtmlStruct "dt" [] hexps1,
HtmlStruct "dd" [] hexps2]
--- Table with a matrix of items where each item is a list of HTML expressions.
table :: [[[HtmlExp]]] -> HtmlExp
table items = HtmlStruct "table" []
(map (\row->HtmlStruct "tr" []
(map (\item -> HtmlStruct "td" [] item) row)) items)
--- Similar to table
but introduces header tags for the first row.
headedTable :: [[[HtmlExp]]] -> HtmlExp
headedTable = withinTable . table
where
withinTable (HtmlStruct "table" attrs (HtmlStruct "tr" rowAttrs row:rows)) =
HtmlStruct "table" attrs
(HtmlStruct "tr" rowAttrs (map addTh row):rows)
addTh x = case x of
(HtmlStruct "td" attrs conts) -> HtmlStruct "th" attrs conts
other -> other
--- Add a row of items (where each item is a list of HTML expressions)
--- as headings to a table. If the first argument is not a table,
--- the headings are ignored.
addHeadings :: HtmlExp -> [[HtmlExp]] -> HtmlExp
addHeadings htable headings = case htable of
HtmlStruct "table" attrs rows ->
HtmlStruct "table" attrs
(HtmlStruct "tr" [] (map (\item->HtmlStruct "th" [] item) headings):rows)
_ -> htable
--- Horizontal rule
hrule :: HtmlExp
hrule = HtmlStruct "hr" [] []
--- Break a line
breakline :: HtmlExp
breakline = HtmlStruct "br" [] []
--- Image
--- @param src - the URL of the image
--- @param alt - the alternative text shown instead of the image
image :: String -> String -> HtmlExp
image src alt = HtmlStruct "img" [("src",src),("alt",htmlQuote alt)] []
-------------- styles and document structuring:
--- Defines a style sheet to be used in this HTML document.
--- @param css - a string in CSS format
styleSheet :: String -> HtmlExp
styleSheet css = HtmlStruct "style" [("type","text/css")] [HtmlText css]
--- Provides a style for HTML elements.
--- The style argument is the name of a style class defined in a
--- style definition (see styleSheet
) or in an
--- external style sheet (see form and page parameters FormCSS
--- and PageCSS
).
--- @param st - name of a style class
--- @param hexps - list of HTML expressions
style :: String -> [HtmlExp] -> HtmlExp
style st hexps = HtmlStruct "span" [("class",st)] hexps
--- Provides a style for a basic text.
--- The style argument is the name of a style class defined in an
--- external style sheet.
--- @param st - name of a style class
--- @param txt - a string (special characters will be quoted)
textstyle :: String -> String -> HtmlExp
textstyle st txt = HtmlStruct "span" [("class",st)] [htxt txt]
--- Provides a style for a block of HTML elements.
--- The style argument is the name of a style class defined in an
--- external style sheet. This element is used (in contrast to "style")
--- for larger blocks of HTML elements since a line break is placed
--- before and after these elements.
--- @param st - name of a style class
--- @param hexps - list of HTML expressions
blockstyle :: String -> [HtmlExp] -> HtmlExp
blockstyle st hexps = HtmlStruct "div" [("class",st)] hexps
--- Joins a list of HTML elements into a single HTML element.
--- Although this construction has no rendering, it is sometimes useful
--- for programming when several HTML elements must be put together.
--- @param hexps - list of HTML expressions
inline :: [HtmlExp] -> HtmlExp
inline hexps = HtmlStruct "span" [] hexps
--- Joins a list of HTML elements into a block.
--- A line break is placed before and after these elements.
--- @param hexps - list of HTML expressions
block :: [HtmlExp] -> HtmlExp
block hexps = HtmlStruct "div" [] hexps
-------------- forms and input fields:
--- Submit button with a label string and an event handler
button :: String -> HtmlHandler -> HtmlExp
button label handler =
HtmlEvent
(HtmlStruct "input" [("type","submit"),("name","EVENT"),
("value",htmlQuote label)] [])
handler
--- Reset button with a label string
resetbutton :: String -> HtmlExp
resetbutton label =
HtmlStruct "input" [("type","reset"),("value",htmlQuote label)] []
--- Submit button in form of an imag.
--- @param src - url of the image
--- @param handler - event handler
imageButton :: String -> HtmlHandler -> HtmlExp
imageButton src handler
= HtmlEvent
(HtmlStruct "input" [("type","image"),("name","EVENT"),("src",src)] [])
handler
--- Input text field with a reference and an initial contents
textfield :: CgiRef -> String -> HtmlExp
textfield cref contents
| cref =:= CgiRef ref -- instantiate cref argument
= HtmlCRef
(HtmlStruct "input" [("type","text"),("name",ref),
("value",htmlQuote contents)] [])
cref
where ref free
--- Input text field (where the entered text is obscured) with a reference
password :: CgiRef -> HtmlExp
password cref
| cref =:= CgiRef ref -- instantiate cref argument
= HtmlCRef
(HtmlStruct "input" [("type","password"),("name",ref)] [])
cref
where
ref free
--- Input text area with a reference, height/width, and initial contents
textarea :: CgiRef -> (Int,Int) -> String -> HtmlExp
textarea cref (height,width) contents
| cref =:= CgiRef ref -- instantiate cref argument
= HtmlCRef
(HtmlStruct "textarea" [("name",ref),
("rows",show height),("cols",show width)]
[htxt contents])
cref
where
ref free
--- A checkbox with a reference and a value.
--- The value is returned if checkbox is on, otherwise "" is returned.
checkbox :: CgiRef -> String -> HtmlExp
checkbox cref value
| cref =:= CgiRef ref -- instantiate cref argument
= HtmlCRef
(HtmlStruct "input" [("type","checkbox"),("name",ref),
("value",htmlQuote value)] [])
cref
where
ref free
--- A checkbox that is initially checked with a reference and a value.
--- The value is returned if checkbox is on, otherwise "" is returned.
checkedbox :: CgiRef -> String -> HtmlExp
checkedbox cref value
| cref =:= CgiRef ref -- instantiate cref argument
= HtmlCRef
(HtmlStruct "input" [("type","checkbox"),("name",ref),
("value",htmlQuote value),("checked","checked")] [])
cref
where
ref free
--- A main button of a radio (initially "on") with a reference and a value.
--- The value is returned of this button is on.
--- A complete radio button suite always consists of a main button
--- (radio_main) and some further buttons (radio_others) with the
--- same reference. Initially, the main button is selected
--- (or nothing is selected if one uses radio_main_off instead of radio_main).
--- The user can select another button but always at most one button
--- of the radio can be selected. The value corresponding to the
--- selected button is returned in the environment for this radio reference.
radio_main :: CgiRef -> String -> HtmlExp
radio_main cref value
| cref =:= CgiRef ref -- instantiate cref argument
= HtmlCRef
(HtmlStruct "input" [("type","radio"),("name",ref),
("value",htmlQuote value),("checked","yes")] [])
cref
where
ref free
--- A main button of a radio (initially "off") with a reference and a value.
--- The value is returned of this button is on.
radio_main_off :: CgiRef -> String -> HtmlExp
radio_main_off cref value
| cref =:= CgiRef ref -- instantiate cref argument
= HtmlCRef
(HtmlStruct "input" [("type","radio"),("name",ref),
("value",htmlQuote value)] [])
cref
where
ref free
--- A further button of a radio (initially "off") with a reference (identical
--- to the main button of this radio) and a value.
--- The value is returned of this button is on.
radio_other :: CgiRef -> String -> HtmlExp
radio_other cref value
| cref =:= CgiRef ref -- instantiate cref argument
= HtmlStruct "input"
[("type","radio"),("name",ref),("value",htmlQuote value)] []
where
ref free
--- A selection button with a reference and a list of name/value pairs.
--- The names are shown in the selection and the value is returned
--- for the selected name.
selection :: CgiRef -> [(String,String)] -> HtmlExp
selection cref menue
| cref =:= CgiRef ref -- instantiate cref argument
= HtmlCRef
(HtmlStruct "select" [("name",ref)]
((concat . map (\(n,v)->[HtmlStruct "option" [("value",v)] [htxt n]]))
menue))
cref
where
ref free
--- A selection button with a reference, a list of name/value pairs,
--- and a preselected item in this list.
--- The names are shown in the selection and the value is returned
--- for the selected name.
--- @param ref - a CGI reference
--- @param nvs - list of name/value pairs
--- @param sel - the index of the initially selected item in the list nvs
--- @return an HTML expression representing the selection button
selectionInitial :: CgiRef -> [(String,String)] -> Int -> HtmlExp
selectionInitial cref sellist sel
| cref =:= CgiRef ref -- instantiate cref argument
= HtmlCRef (HtmlStruct "select" [("name",ref)] (selOption sellist sel))
cref
where
ref free
selOption [] _ = []
selOption ((n,v):nvs) i =
HtmlStruct "option"
([("value",v)] ++ if i==0 then [("selected","selected")] else [])
[htxt n] : selOption nvs (i-1)
--- A selection button with a reference and a list of name/value/flag pairs.
--- The names are shown in the selection and the value is returned
--- if the corresponding name is selected. If flag is True, the
--- corresonding name is initially selected. If more than one name
--- has been selected, all values are returned in one string
--- where the values are separated by newline (`'\n'`) characters.
multipleSelection :: CgiRef -> [(String,String,Bool)] -> HtmlExp
multipleSelection cref sellist
| cref =:= CgiRef ref -- instantiate cref argument
= HtmlCRef (HtmlStruct "select" [("name",ref),("multiple","multiple")]
(map selOption sellist))
cref
where
ref free
selOption (n,v,flag) =
HtmlStruct "option"
([("value",v)] ++ if flag then [("selected","selected")] else [])
[htxt n]
--- A hidden field to pass a value referenced by a fixed name.
--- This function should be used with care since it may cause
--- conflicts with the CGI-based implementation of this library.
hiddenfield :: String -> String -> HtmlExp
hiddenfield name value =
HtmlStruct "input" [("type","hidden"),("name",name),("value",value)] []
------------------------------------------------------------------------------
--- Quotes special characters (`<`,`>`,`&`,`"`, umlauts) in a string
--- as HTML special characters.
htmlQuote :: String -> String
htmlQuote [] = []
htmlQuote (c:cs) | c=='<' = "<" ++ htmlQuote cs
| c=='>' = ">" ++ htmlQuote cs
| c=='&' = "&" ++ htmlQuote cs
| c=='"' = """ ++ htmlQuote cs
| otherwise = htmlIsoUmlauts [c] ++ htmlQuote cs
--- Translates umlauts in iso-8859-1 encoding into HTML special characters.
htmlIsoUmlauts :: String -> String
htmlIsoUmlauts [] = []
htmlIsoUmlauts (c:cs) | oc==228 = "ä" ++ htmlIsoUmlauts cs
| oc==246 = "ö" ++ htmlIsoUmlauts cs
| oc==252 = "ü" ++ htmlIsoUmlauts cs
| oc==196 = "Ä" ++ htmlIsoUmlauts cs
| oc==214 = "Ö" ++ htmlIsoUmlauts cs
| oc==220 = "Ü" ++ htmlIsoUmlauts cs
| oc==223 = "ß" ++ htmlIsoUmlauts cs
| oc==197 = "Å" ++ htmlIsoUmlauts cs
| oc==250 = "ú"++ htmlIsoUmlauts cs
| oc==237 = "í"++ htmlIsoUmlauts cs
| oc==225 = "á"++ htmlIsoUmlauts cs
| otherwise = c : htmlIsoUmlauts cs
where oc = ord c
------------------------------------------------------------------------------
--- Adds an attribute (name/value pair) to an HTML element.
addAttr :: HtmlExp -> (String,String) -> HtmlExp
addAttr hexp attr = addAttrs hexp [attr]
--- Adds a list of attributes (name/value pair) to an HTML element.
addAttrs :: HtmlExp -> [(String,String)] -> HtmlExp
addAttrs (HtmlText s) _ = HtmlText s -- strings have no attributes
addAttrs (HtmlStruct tag attrs hexps) newattrs =
HtmlStruct tag (attrs++newattrs) hexps
addAttrs (HtmlEvent hexp handler) attrs =
HtmlEvent (addAttrs hexp attrs) handler
addAttrs (HtmlCRef hexp cref) attrs =
HtmlCRef (addAttrs hexp attrs) cref
addAttrs (AjaxEvent id handler) _ = AjaxEvent id handler
addAttrs (AjaxEvent2 hexp handler str1 str2) attrs =
AjaxEvent2 (addAttrs hexp attrs) handler str1 str2
--- Adds a class attribute to an HTML element.
addClass :: HtmlExp -> String -> HtmlExp
addClass hexp cls = addAttr hexp ("class",cls)
------------------------------------------------------------------------------
-- Auxiliaries for faster show (could be later put into a standard library)
type ShowS = String -> String
showString :: String -> String -> String
showString s = (s++)
showChar :: Char -> String -> String
showChar c = (c:)
nl :: String -> String
nl = showChar '\n'
concatS :: [a -> a] -> a -> a
concatS [] = id
concatS xs@(_:_) = foldr1 (\ f g -> f . g) xs
------------------------------------------------------------------------------
--- Transforms a list of HTML expressions into string representation.
showHtmlExps :: [HtmlExp] -> String
showHtmlExps hexps = showsHtmlExps 0 hexps ""
-- get the string contents of an HTML expression:
getText :: HtmlExp -> String
getText (HtmlText s) = s
getText (HtmlStruct _ _ _) = ""
getText (HtmlEvent he _) = getText he
getText (HtmlCRef he _) = getText he
getText (AjaxEvent _ _) = ""
getText (AjaxEvent2 _ _ _ _) = ""
-- get the (last) tag of an HTML expression:
getTag :: HtmlExp -> String
getTag (HtmlText _) = ""
getTag (HtmlStruct tag _ _) = tag
getTag (HtmlEvent he _) = getTag he
getTag (HtmlCRef he _) = getTag he
getTag (AjaxEvent _ _) = ""
getTag (AjaxEvent2 _ _ _ _) = ""
-- is this a tag where a line break can be safely added?
tagWithLn :: String -> Bool
tagWithLn t = t/="" &&
t `elem` ["br","p","li","ul","ol","dl","dt","dd","hr",
"h1","h2","h3","h4","h5","h6","div",
"html","title","head","body","link","meta","script",
"form","table","tr","td"]
--- Transforms a single HTML expression into string representation.
showHtmlExp :: HtmlExp -> String
showHtmlExp hexp = showsHtmlExp 0 hexp ""
--- HTML tags that have no end tag in HTML:
noEndTags :: [String]
noEndTags = ["img","input","link","meta"]
showsHtmlExp :: Int -> HtmlExp -> ShowS
showsHtmlExp _ (HtmlText s) = showString s
showsHtmlExp i (HtmlStruct tag attrs hexps) =
let maybeLn j = if tagWithLn tag then nl . showTab j else id
in maybeLn i .
(if null hexps && (null attrs || tag `elem` noEndTags)
then showsHtmlOpenTag tag attrs "/>"
else showsHtmlOpenTag tag attrs ">" . maybeLn (i+2) . showExps hexps .
maybeLn i . showString "" . showString tag . showChar '>'
) . maybeLn i
where
showExps = if tag=="pre"
then concatS . map (showsHtmlExp 0) else showsHtmlExps (i+2)
showsHtmlExp i (HtmlEvent hexp _) = showsHtmlExp i hexp
showsHtmlExp i (HtmlCRef hexp _) = showsHtmlExp i hexp
showsHtmlExp _ (AjaxEvent _ _) = showString ""
showsHtmlExp _ (AjaxEvent2 _ _ _ _) = showString ""
showsHtmlExps :: Int -> [HtmlExp] -> ShowS
showsHtmlExps _ [] = id
showsHtmlExps i (he:hes) = showsWithLnPrefix he . showsHtmlExps i hes
where
showsWithLnPrefix hexp = let s = getText hexp
in if s/="" && isSpace (head s)
then nl . showTab i . showString (tail s)
else showsHtmlExp i hexp
showTab :: Int -> String -> String
showTab n = showString (take n (repeat ' '))
showsHtmlOpenTag :: String -> [(String,String)] -> String -> ShowS
showsHtmlOpenTag tag attrs close =
showChar '<' . showString tag .
concatS (map attr2string attrs) . showString close
where
attr2string (attr,value) = showChar ' ' . showString attr .
showString "=\"" . encodeQuotes value . showChar '"'
-- encode double quotes as """:
encodeQuotes [] = id
encodeQuotes (c:cs) | c=='"' = showString """ . encodeQuotes cs
| otherwise = showChar c . encodeQuotes cs
------------------------------------------------------------------------------
--- Transforms HTML page into string representation.
--- @param page - the HTML page
--- @return string representation of the HTML document
showHtmlPage :: HtmlPage -> String
showHtmlPage (HtmlPage title params html) =
htmlPrelude ++
showHtmlExp (HtmlStruct "html" htmlTagAttrs
[HtmlStruct "head" []
([HtmlStruct "title" [] [HtmlText (htmlQuote title)]] ++
concatMap param2html params),
HtmlStruct "body" bodyattrs html])
where
param2html (PageEnc enc) =
[HtmlStruct "meta" [("http-equiv","Content-Type"),
("content","text/html; charset="++enc)] []]
param2html (PageCSS css) =
[HtmlStruct "link" [("rel","stylesheet"),("type","text/css"),("href",css)]
[]]
param2html (PageJScript js) =
[HtmlStruct "script" [("type","text/javascript"),("src",js)] []]
param2html (PageMeta attrs) = [HtmlStruct "meta" attrs []]
param2html (PageLink attrs) = [HtmlStruct "link" attrs []]
param2html (PageBodyAttr _) = [] -- these attributes are separately processed
bodyattrs = [attr | (PageBodyAttr attr) <- params]
--- Standard header for generated HTML pages.
htmlPrelude :: String
htmlPrelude = "\n"
--- Standard attributes for element "html".
htmlTagAttrs :: [(String,String)]
htmlTagAttrs = [("lang","en")]
------------------------------------------------------------------------------
--- Gets the parameter attached to the URL of the script.
--- For instance, if the script is called with URL
--- "http://.../script.cgi?parameter", then "parameter" is
--- returned by this I/O action.
--- Note that an URL parameter should be "URL encoded" to avoid
--- the appearance of characters with a special meaning.
--- Use the functions "urlencoded2string" and "string2urlencoded"
--- to decode and encode such parameters, respectively.
getUrlParameter :: IO String
getUrlParameter = getEnviron "QUERY_STRING"
--- Translates urlencoded string into equivalent ASCII string.
urlencoded2string :: String -> String
urlencoded2string [] = []
urlencoded2string (c:cs)
| c == '+' = ' ' : urlencoded2string cs
| c == '%' = chr (maybe 0 fst (readHex (take 2 cs)))
: urlencoded2string (drop 2 cs)
| otherwise = c : urlencoded2string cs
--- Translates arbitrary strings into equivalent urlencoded string.
string2urlencoded :: String -> String
string2urlencoded [] = []
string2urlencoded (c:cs)
| isAlphaNum c = c : string2urlencoded cs
| c == ' ' = '+' : string2urlencoded cs
| otherwise = let oc = ord c
in '%' : int2hex(oc `div` 16) : int2hex(oc `mod` 16) : string2urlencoded cs
where
int2hex i = if i<10 then chr (ord '0' + i)
else chr (ord 'A' + i - 10)
------------------------------------------------------------------------------
--- Gets the cookies sent from the browser for the current CGI script.
--- The cookies are represented in the form of name/value pairs since
--- no other components are important here.
getCookies :: IO [(String,String)]
getCookies =
do cookiestring <- getEnviron "HTTP_COOKIE"
return $ parseCookies cookiestring
-- translate a string of cookies (of the form "NAME1=VAL1; NAME2=VAL")
-- into a list of name/value pairs:
parseCookies :: String -> [(String,String)]
parseCookies str = if str=="" then [] else
let (c1,cs) = break (==';') str
in parseCookie c1 :
parseCookies (dropWhile (==' ') (if cs=="" then "" else tail cs))
where
parseCookie s = let (name,evalue) = break (=='=') s in
(name,if evalue=="" then "" else urlencoded2string (tail evalue))
--- For image buttons: retrieve the coordinates where the user clicked
--- within the image.
coordinates :: CgiEnv -> Maybe (Int,Int)
coordinates env = let x = env (CgiRef "x")
y = env (CgiRef "y")
in if x/="" && y/=""
then Just (tryReadNat 0 x, tryReadNat 0 y)
else Nothing
------------------------------------------------------------------------------
--- The server implementing an HTML form (possibly containing input fields).
--- It receives a message containing the environment of the client's
--- web browser, translates the HTML form w.r.t. this environment
--- into a string representation of the complete HTML document
--- and sends the string representation back to the client's browser
--- by binding the corresponding message argument.
--- @param url - the URL of this executable.
--- @param cgikey - a unique key to identify this CGI script (used for safe
--- storing of event handlers in this server)
--- @param hformact - an IO action returning an HTML form
runFormServerWithKey :: String -> String -> IO HtmlForm -> IO ()
runFormServerWithKey url cgikey hformact =
runFormServerWithKeyAndFormParams url cgikey [] hformact
--- The server implementing an HTML form (possibly containing input fields).
--- It receives a message containing the environment of the client's
--- web browser, translates the HTML form w.r.t. this environment
--- into a string representation of the complete HTML document
--- and sends the string representation back to the client's browser
--- by binding the corresponding message argument.
--- @param url - the URL of this executable.
--- @param cgikey - a unique key to identify this CGI script (used for safe
--- storing of event handlers on the web server)
--- @param formparams - form parameters added to the initial and all
--- subsequent forms
--- @param hformact - an IO action returning an HTML form
runFormServerWithKeyAndFormParams :: String -> String -> [FormParam]
-> IO HtmlForm -> IO ()
runFormServerWithKeyAndFormParams url cgikey formparams hformact = do
args <- getArgs
let (timeout,rargs) = stripTimeoutArg args
case rargs of
["-port",port,"-scriptkey",skey] -> startCgiServer timeout port skey
_ -> putErrLn $ "ERROR: cgi server called with illegal arguments"
where
stripTimeoutArg args = case args of
("-servertimeout":tos:rargs) ->
(tryReadNat defaultCgiServerTimeout tos, rargs)
_ -> (defaultCgiServerTimeout,args)
startCgiServer timeout port scriptkey = do
time <- getClockTime
ltime <- toCalendarTime time
(state,htmlstring) <- computeFormInStateAndEnv url cgikey formparams
(initialServerState time) scriptkey hformact []
putStr htmlstring
hClose stdout
if isServerStateWithoutHandlers state
then done
else -- start server process:
do let portname = port++scriptkey
socket <- listenOn portname
putErrLn $ calendarTimeToString ltime ++
": server started on port " ++ portname
registerCgiServer url portname
serveCgiMessagesForForm timeout url cgikey portname formparams
hformact socket state
-- The default timeout period for the cgi server in milliseconds:
defaultCgiServerTimeout :: Int
defaultCgiServerTimeout = 7200000 -- two hours
-- The main server loop:
serveCgiMessagesForForm :: Int -> String -> String -> String -> [FormParam]
-> IO HtmlForm -> Socket -> ServerState -> IO ()
serveCgiMessagesForForm servertimeout url cgikey portname
fparams initform socket = serveCgiMessages
where
serveCgiMessages state =
if isServerStateWithoutHandlers state
then do -- terminate server due to inactivity
ltime <- getLocalTime
putErrLn $ calendarTimeToString ltime ++
": terminated due to empty handler list"
unregisterCgiServer portname
sClose socket
else waitForSocketAccept socket servertimeout >>=
maybe (do -- terminate server due to inactivity
ltime <- getLocalTime
putErrLn $ calendarTimeToString ltime ++
": terminated due to timeout"
unregisterCgiServer portname
sClose socket )
(\ (rhost,hdl) -> do
hostname <- getHostname
if rhost `elem` ["localhost","localhost.localdomain",hostname]
|| take 8 rhost == "127.0.0."
then readCgiServerMsg hdl >>=
maybe (hClose hdl >> serveCgiMessages state)
(serveCgiMessage state hdl)
else putErrLn ("Ignored message from: "++rhost) >>
hClose hdl >> serveCgiMessages state )
-- Process the received CgiServerMsg:
serveCgiMessage _ hdl StopCgiServer = do
hClose hdl
ltime <- getLocalTime
putErrLn $ calendarTimeToString ltime ++
": server terminated by stop message"
unregisterCgiServer portname
sClose socket
serveCgiMessage state hdl CleanServer = do
hClose hdl
nstate <- cleanOldEventHandlers state
serveCgiMessages nstate
serveCgiMessage oldstate hdl GetLoad = do
state <- cleanOldEventHandlers oldstate
serverload <- getServerLoad state
hPutStrLn hdl serverload
hClose hdl
serveCgiMessages state
serveCgiMessage oldstate hdl SketchStatus = do
state <- cleanOldEventHandlers oldstate
serverstatus <- getServerStatus state
hPutStrLn hdl serverstatus
hClose hdl
serveCgiMessages state
serveCgiMessage state hdl SketchHandlers =
reportStatus state hdl sketchEventHandler
where
sketchEventHandler (key,time,_,_,gkey) = do
ltime <- toCalendarTime time
return $ "No. " ++ show key ++ " (" ++ showGroupKey gkey ++
"), expires at: " ++
calendarTimeToString ltime ++ "\n"
serveCgiMessage state hdl ShowStatus =
reportStatus state hdl showEventHandler
where
showEventHandler (key,time,_,(_,_{-handler-}),gkey) = do
ltime <- toCalendarTime time
return $ "No. " ++ show key ++ " (" ++ showGroupKey gkey ++
"), expires at " ++
calendarTimeToString ltime ++ ": " ++
--showAnyQExpression handler ++ "\n"
"\n"
serveCgiMessage state hdl (CgiSubmit scriptenv formenv) = do
let scriptkey = maybe "" id (lookup "SCRIPTKEY" scriptenv)
mapIO_ (\(var,val) -> if var=="SCRIPTKEY" then done
else setEnviron var val)
scriptenv
if null formenv -- initial form?
then serveFormInEnv state scriptkey initform []
else do
(rstate,mfe) <- getNextFormAndCgiEnv state cgikey formenv
maybe (do urlparam <- getUrlParameter
hPutStrLn hdl (noHandlerPage url urlparam)
hClose hdl
serveCgiMessages rstate)
(\ (ioform,env) -> serveFormInEnv rstate scriptkey ioform env )
mfe
where
serveFormInEnv rstate scriptkey hformact cenv = do
(nstate,htmlstring) <- computeFormInStateAndEnv url cgikey fparams
rstate scriptkey hformact cenv
hPutStrLn hdl htmlstring
hClose hdl
serveCgiMessages nstate
reportStatus state@(stime,maxkey,ctime,ehs) hdl eh2string = do
lstime <- toCalendarTime stime
lctime <- toCalendarTime ctime
ehsstrings <- mapIO eh2string ehs
hPutStrLn hdl $ "Started at: " ++ calendarTimeToString lstime ++ "\n" ++
"Next cleanup: " ++ calendarTimeToString lctime ++
" (maxkey: " ++ show maxkey ++")\n"++
"Current event handlers:\n" ++ concat ehsstrings
hClose hdl
serveCgiMessages state
-- computes a HTML form w.r.t. a state and a cgi environment:
computeFormInStateAndEnv
:: String -> String -> [FormParam] -> ServerState -> String
-> IO HtmlForm -> [(String,String)] -> IO (ServerState,String)
computeFormInStateAndEnv url cgikey fparams state scriptkey hformact cenv =
catch tryComputeForm
(\e -> do uparam <- getUrlParameter
return (state,errorAsHtml e uparam))
where
errorAsHtml e urlparam = addHtmlContentType $ showHtmlPage $
page "Server Error"
[h1 [htxt "Error: Failure during computation"],
par [htxt "Your request cannot be processed due to a run-time error:"],
pre [htxt (showError e)],
par [htxt "You can try to ",
href (url ++ if null urlparam then "" else '?':urlparam)
[htxt "click here"],
htxt " to try again loading the web page or inform the web ",
htxt "administrator about this problem."]]
tryComputeForm = do
cform <- hformact
let (cookiestring,hform) = extractCookies cform
(htmlstring,evhs) <- showAnswerFormInEnv url scriptkey
(addFormParams hform fparams)
(getMaxFieldNr cenv + 1)
nstate <- storeEnvHandlers state
(formWithMultipleHandlers hform)
(encodeKey cgikey)
(filter (\ (t,_) -> t/="DEFAULT" && take 6 t /= "EVENT_") cenv)
evhs
seq (isList htmlstring) done -- to ensure to catch all failures here
return (nstate, cookiestring++htmlstring)
isList [] = True
isList (_:xs) = isList xs
formWithMultipleHandlers :: HtmlForm -> Bool
formWithMultipleHandlers (HtmlAnswer _ _) = False
formWithMultipleHandlers (HtmlForm _ params _) = any isMultipleHandlers params
where
isMultipleHandlers formparam =
case formparam of MultipleHandlers -> True
_ -> False
formWithMultipleHandlers (AjaxAnswer _ _) = True
-- Encode an arbitrary string to make it less readable.
-- Used for encoding CGI keys before storing them on the web server.
encodeKey :: String -> String
encodeKey = map mapchr . reverse . filter (not . isSpace)
where
mapchr c | oc<33 || oc>126 = c
| oc<114 = chr (oc+13)
| otherwise = chr (oc-81)
where oc = ord c
-- Puts a line to stderr:
putErrLn :: String -> IO ()
putErrLn s = hPutStrLn stderr s >> hFlush stderr
--------------------------------------------------------------------------
-- Auxiliaries to implement the cgi script server:
-- get the next form and environment from a current environment (specifying a
-- user-selected event handler) and a server state holding all event handlers:
getNextFormAndCgiEnv :: ServerState -> String -> [(String,String)]
-> IO (ServerState, Maybe (IO HtmlForm,[(String,String)]))
getNextFormAndCgiEnv state cgikey newcenv = do
(nstate,mbh) <- retrieveEnvHandlers state (encodeKey cgikey)
(urlencoded2string (getFormEvent "" newcenv))
return $ maybe (nstate,Nothing)
(\ (oldcenv,handler) -> let cenv = newcenv++oldcenv in
(nstate, Just (handler (cgiGetValue cenv), cenv)))
mbh
-- put the HTML string corresponding to an HtmlForm with HTTP header on stdout:
showAnswerFormInEnv :: String -> String -> HtmlForm -> Int
-> IO (String,[(HtmlHandler,String)])
showAnswerFormInEnv url key hform@(HtmlForm _ _ _) crefnr = do
(htmlstring,evhs) <- showHtmlFormInEnv url key hform crefnr
return (addHtmlContentType htmlstring, evhs)
showAnswerFormInEnv _ _ (HtmlAnswer ctype cont) _ = do
return ("Content-Length: " ++ show (length cont) ++
"\nContent-Type: "++ctype++"\n\n"++cont, [])
showAnswerFormInEnv _ _ (AjaxAnswer cont nvsAndhexps) crefnr = do
(pairs,evhs) <- converttohtml ([],[]) nvsAndhexps crefnr
let jsonpairs = map (\ (nvs,html) ->
Object ((map (\ (n,v) -> (n,String v)) nvs) ++
[("html",String html)]) ) pairs
return ("Content-Type: text/json\n\n" ++
(showJson $ Object [("content",cont),("popups",Array jsonpairs)]),evhs)
converttohtml :: ([(a,String)],[(HtmlHandler,String)])
-> [(a, [HtmlExp])] -> Int
-> IO ([(a, String)], [(HtmlHandler,String)])
converttohtml (xs,evhs) [] _ = return (xs,evhs)
converttohtml (xs,evhs) ((nvs,hexp):ys) crefnr = do
(htmlstring,newevhs,newrefnr) <- htmlForm2html_ hexp crefnr
converttohtml ((nvs,(showHtmlExps htmlstring)):xs,evhs++newevhs) ys newrefnr
------------------------------------------------------------------------------
htmlForm2html_ :: [HtmlExp] -> Int
-> IO ([HtmlExp],[(HtmlHandler,String)],Int)
htmlForm2html_ html crefnr = do
let (htmlwithoutcrefs,newrefnr) = numberCgiRefs html crefnr
-- enforce instantiation before handlers are stored:
seq newrefnr done
--seq (normalForm htmlwithoutcrefs) done
let (transhtml, evhs, _) = translateHandlers htmlwithoutcrefs
--storeEventHandlers cgikey oldcenv evhs
return (transhtml, evhs, newrefnr)
------------------------------------------------------------------------------
-- Adds the initial content lines (including content length) to an HTML string.
addHtmlContentType :: String -> String
addHtmlContentType htmlstring =
"Content-Length: " ++ show (length htmlstring) ++ "\n" ++
"Content-Type: text/html\n\n" ++ htmlstring
-- return the HTML string corresponding to an HtmlForm:
showHtmlFormInEnv :: String -> String -> HtmlForm -> Int
-> IO (String,[(HtmlHandler,String)])
showHtmlFormInEnv url key (HtmlForm ftitle fparams fhexp) crefnr = do
qstr <- getEnviron "QUERY_STRING"
--putStrLn (showHtmlExps [pre [par (env2html cenv),hrule]]) --debug
(title,params,hexps,firsthandler,evhs) <-
htmlForm2html (HtmlForm ftitle fparams fhexp) crefnr
return (showForm (if null evhs
then []
else [("SCRIPTKEY",key),("DEFAULT","EVENT_"++firsthandler)])
(if qstr=="" then url else url++"?"++qstr)
(HtmlForm title params hexps),
evhs)
-- extract the cookies contained in a form and return the "set cookie" string
-- and the form without the cookies:
extractCookies :: HtmlForm -> (String,HtmlForm)
extractCookies (HtmlAnswer ctype cont) = ("",HtmlAnswer ctype cont)
extractCookies (HtmlForm title params hexp) =
let cookiestring = if null cookies
then ""
else "Cache-control: no-cache=\"set-cookie\"\n" ++
concatMap ((++"\n") . formatCookie) cookies
in (cookiestring, HtmlForm title otherparams hexp)
where
(cookies,otherparams) = splitFormParams params
splitFormParams [] = ([],[])
splitFormParams (fparam:fps) =
let (cs,ops) = splitFormParams fps
in case fparam of
FormCookie n v ps -> ((n,v,ps):cs,ops)
_ -> (cs,fparam:ops)
extractCookies (AjaxAnswer x y) = ("",AjaxAnswer x y)
-- get the EVENT_ definition of the cgi environment
-- (or "DEFAULT" value if it is not there):
getFormEvent :: String -> [(String,String)] -> String
getFormEvent deflt [] = deflt
getFormEvent deflt ((tag,val):tvs) =
if tag == "DEFAULT" then getFormEvent (drop 6 val) tvs else
if take 6 tag == "EVENT_" then urlencoded2string (drop 6 tag)
else getFormEvent deflt tvs
-- compute the maximal field number of all "FIELD_nr" in a CGI environment:
getMaxFieldNr :: [(String,String)] -> Int
getMaxFieldNr [] = 0
getMaxFieldNr ((name,_):env) =
if take 6 name == "FIELD_"
then max (tryReadNat 0 (drop 6 name)) (getMaxFieldNr env)
else getMaxFieldNr env
-- try to read a natural number in a string or return first argument:
tryReadNat :: Int -> String -> Int
tryReadNat d s = maybe d (\(i,rs)->if null rs then i else d) (readNat s)
-- get the value assigned to a name in a given cgi environment
cgiGetValue :: [(String,String)] -> CgiRef -> String
cgiGetValue cenv (CgiRef ref) =
concat (intersperse "\n" (map snd (filter ((ref==) . fst) cenv)))
-- transform HTML form into HTML document (by instantiating CgiRefs
-- (starting with the second argument) and modifying event handlers):
-- (Result: title/HTML document/form params/encoded first handler)
htmlForm2html :: HtmlForm -> Int
-> IO (String,[FormParam],[HtmlExp],String,[(HtmlHandler,String)])
htmlForm2html (HtmlForm title params html) crefnr = do
let (htmlwithoutcrefs,newrefnr) = numberCgiRefs html crefnr
-- enforce instantiation before handlers are stored:
seq newrefnr done
-- seq (normalForm htmlwithoutcrefs) done
let (transhtml, evhs, fh) = translateHandlers htmlwithoutcrefs
--storeEventHandlers cgikey oldcenv evhs
return (title, params, transhtml, fh, evhs)
-- instantiate all CgiRefs with a unique tag in HTML expressions:
numberCgiRefs :: [HtmlExp] -> Int -> ([HtmlExp],Int)
-- arguments: HTMLExps, number for cgi-refs
-- result: translated HTMLExps, new number for cgi-refs
numberCgiRefs [] i = ([],i)
numberCgiRefs (HtmlText s : hexps) i =
case numberCgiRefs hexps i of
(nhexps,j) -> (HtmlText s : nhexps, j)
numberCgiRefs (HtmlStruct tag attrs hexps1 : hexps2) i =
case numberCgiRefs hexps1 i of
(nhexps1,j) -> case numberCgiRefs hexps2 j of
(nhexps2,k) -> (HtmlStruct tag attrs nhexps1 : nhexps2, k)
numberCgiRefs (HtmlEvent (HtmlStruct tag attrs hes) handler : hexps) i =
case numberCgiRefs hexps i of
(nhexps,j) -> (HtmlEvent (HtmlStruct tag attrs hes) handler : nhexps, j)
numberCgiRefs (HtmlCRef hexp (CgiRef ref) : hexps) i
| ref =:= ("FIELD_"++show i)
= case numberCgiRefs [hexp] (i+1) of
([nhexp],j) -> case numberCgiRefs hexps j of
(nhexps,k) -> (nhexp : nhexps, k)
numberCgiRefs (AjaxEvent id handler: hexps) i =
let (nhexps,j) = numberCgiRefs hexps i
in (AjaxEvent id handler : nhexps, j)
numberCgiRefs (AjaxEvent2 hexp handler str1 str2 : hexps) i =
let (nhexps1,j) = numberCgiRefs [hexp] i
(nhexps2,k) = numberCgiRefs hexps j
in (AjaxEvent2 (head nhexps1) handler str1 str2 : nhexps2, k)
-- translate all event handlers into their internal form:
-- (assumption: all CgiRefs have already been instantiated and eliminated)
-- the result is the translated HTML expression list (without HtmlEvents),
-- the list of event handlers and their corresponding logical variables
-- denoting the key that is inserted for the event handler in the translated
-- HTML expression, and the string encoding of the first event handler
-- (for the default handler)
translateHandlers :: [HtmlExp] -> ([HtmlExp],[(HtmlHandler,String)],String)
translateHandlers [] = ([],[],"")
translateHandlers (HtmlText s : hexps) =
let (nhexps,evhs,fh) = translateHandlers hexps
in (HtmlText s : nhexps, evhs, fh)
translateHandlers (HtmlStruct tag attrs hexps1 : hexps2) =
let (nhexps1,evhs1,fh1) = translateHandlers hexps1
(nhexps2,evhs2,fh2) = translateHandlers hexps2
in (HtmlStruct tag attrs nhexps1 : nhexps2, evhs1++evhs2,
if fh1=="" then fh2 else fh1)
translateHandlers (HtmlEvent (HtmlStruct tag attrs hes) handler : hexps) =
let (nhexps,evhs,_) = translateHandlers hexps
fh = string2urlencoded key
in (HtmlStruct tag (changeAssoc attrs "name" ("EVENT_" ++ fh)) hes : nhexps,
(handler,key):evhs, fh)
where key free
translateHandlers (AjaxEvent key handler : hexps) =
let (nhexps,evhs,_) = translateHandlers hexps
fh = string2urlencoded key
in (nhexps, (handler,key):evhs, fh)
translateHandlers (AjaxEvent2 hexp handler str1 str2 : hexps) =
let (nhexps1,evhs1,_) = translateHandlers [hexp]
(nhexps2,evhs2,_) = translateHandlers hexps
fh = string2urlencoded key
changeAttr (HtmlStruct tag attrs hes) =
if null str2
then HtmlStruct tag (changeAssoc attrs str1 ("EVENT_" ++ fh)) hes
else HtmlStruct tag
(changeAssoc attrs str1
(str2 ++ "(event,window,'EVENT_" ++ fh ++ "');")) hes
changeAttr (AjaxEvent2 he hdlr s1 s2) =
AjaxEvent2 (changeAttr he) hdlr s1 s2
changeAttr (HtmlEvent he hdlr) = HtmlEvent (changeAttr he) hdlr
--changeAttr (HtmlCRef he ref) = HtmlCRef (changeAttr he) ref
--changeAttr (HtmlText str) = HtmlText str
in (changeAttr (head nhexps1) : nhexps2,(handler,key):evhs1++evhs2, fh)
where key free
-- show a HTML form in String representation:
showForm :: [(String,String)] -> String -> HtmlForm -> String
showForm cenv url (HtmlForm title params html) =
htmlPrelude ++
showHtmlExp
(HtmlStruct "html" htmlTagAttrs
[HtmlStruct "head" []
([HtmlStruct "title" [] [HtmlText (htmlQuote title)]] ++
concatMap param2html paramsWithEncoding),
HtmlStruct "body" bodyattrs
((if null url || null cenv then id
else \he->[HtmlStruct "form"
([("method","post"),("action",url)]
++ onsubmitattr ++ targetattr)
he])
( --[par (env2html cenv),hrule] ++ -- debug
cenv2hidden cenv ++
html))])
where
paramsWithEncoding = if null [e | (FormEnc e) <- params]
then FormEnc defaultEncoding : params
else params
param2html (FormEnc enc) =
[HtmlStruct "meta" [("http-equiv","Content-Type"),
("content","text/html; charset="++enc)] []]
param2html (FormCSS css) =
[HtmlStruct "link" [("rel","stylesheet"),("type","text/css"),("href",css)]
[]]
param2html (FormMeta attrs) = [HtmlStruct "meta" attrs []]
param2html (FormJScript js) =
[HtmlStruct "script" [("type","text/javascript"),("src",js)] []]
param2html (FormOnSubmit _) = []
param2html (FormTarget _) = []
-- no rule for FormCookie since they have been already processed
param2html (HeadInclude hexp) = [hexp]
param2html MultipleHandlers = []
param2html (BodyAttr _) = []
-- no rule for BodyAttr since it is considered later
bodyattrs = [ps | (BodyAttr ps) <- params]
onsubmit = [s | (FormOnSubmit s) <- params]
onsubmitattr = if null onsubmit then [] else [("onsubmit",head onsubmit)]
target = [s | (FormTarget s) <- params]
targetattr = if null target then [] else [("target",head target)]
-- translate cgi environment into HTML (for debugging purposes):
env2html :: [(String,String)] -> [HtmlExp]
env2html env = concat (map (\(n,v)->[htxt (n++": "++v),breakline]) env)
-- translate environment into hidden fields (without EVENT field!):
-- (note: the field values are urlencoded to avoid problems
-- with passing special characters; moreover, the names of fields
-- containing urlencoded values are prefixed by "U")
cenv2hidden :: [(String,String)] -> [HtmlExp]
cenv2hidden env = concat (map pair2hidden env)
where
pair2hidden (n,v)
| take 6 n == "EVENT_" = []
| take 6 n == "FIELD_" = [hiddenfield ('U':n) (string2urlencoded v)]
| otherwise = [hiddenfield n v]
------------------------------------------------------------------------------
-- association lists (list of tag/value pairs):
-- change an associated value (or add association, if not there):
changeAssoc :: Eq tt => [(tt,tv)] -> tt -> tv -> [(tt,tv)]
changeAssoc [] tag val = [(tag,val)]
changeAssoc ((tag1,val1):tvs) tag val =
if tag1 == tag then (tag,val) : tvs
else (tag1,val1) : changeAssoc tvs tag val
------------------------------------------------------------------------------
--- Transforms HTML expressions into LaTeX string representation.
showLatexExps :: [HtmlExp] -> String
showLatexExps hexps = concat (map showLatexExp hexps)
--- Transforms an HTML expression into LaTeX string representation.
showLatexExp :: HtmlExp -> String
showLatexExp (HtmlText s) = "{" ++ specialchars2tex s ++ "}"
showLatexExp (HtmlStruct tag attrs htmlexp)
| tag=="html" = showLatexExps htmlexp
| tag=="head" = "" -- ignore header
| tag=="body" = showLatexExps htmlexp
| tag=="form" = showLatexExps htmlexp
| tag=="h1" = "\\section*{" ++ showLatexExps htmlexp ++ "}\n"
| tag=="h2" = "\\subsection*{" ++ showLatexExps htmlexp ++ "}\n"
| tag=="h3" = "\\subsubsection*{" ++ showLatexExps htmlexp ++ "}\n"
| tag=="h4" = "\\paragraph*{" ++ showLatexExps htmlexp ++ "}\n"
| tag=="h5" = "\\subparagraph*{" ++ showLatexExps htmlexp ++ "}\n"
| tag=="p" = showLatexExps htmlexp ++ "\\par\n"
| tag=="b" = "{\\bf " ++ showLatexExps htmlexp ++ "}"
| tag=="em" = "\\emph{" ++ showLatexExps htmlexp ++ "}"
| tag=="i" = "{\\it " ++ showLatexExps htmlexp ++ "}"
| tag=="tt" = "{\\tt " ++ showLatexExps htmlexp ++ "}"
| tag=="code" = "{\\tt " ++ showLatexExps htmlexp ++ "}"
| tag=="center" = latexEnvironment "center" (showLatexExps htmlexp)
| tag=="pre" = latexEnvironment "verbatim" (textOf htmlexp)
| tag=="font" = showLatexExps htmlexp -- ignore font changes
| tag=="address" = showLatexExps htmlexp
| tag=="blink" = showLatexExps htmlexp
| tag=="sub" = "$_{\\mbox{" ++ showLatexExps htmlexp ++ "}}$"
| tag=="sup" = "$^{\\mbox{" ++ showLatexExps htmlexp ++ "}}$"
| tag=="a" = showLatexExps htmlexp ++
-- add href attribute as footnote, if present:
maybe ""
(\url->"\\footnote{\\tt "++specialchars2tex url++"}\n")
(findHtmlAttr "href" attrs)
| tag=="ul" = latexEnvironment "itemize" (showLatexExps htmlexp)
| tag=="ol" = latexEnvironment "enumerate" (showLatexExps htmlexp)
| tag=="li" = "\\item\n" ++ showLatexExps htmlexp ++ "\n"
| tag=="dl" = latexEnvironment "description" (showLatexExps htmlexp)
| tag=="dt" = "\\item[" ++ showLatexExps htmlexp ++ "]~\\\\\n"
| tag=="dd" = showLatexExps htmlexp
-- tables will be set using the longtable environment,
-- (The package longtable is added by default to every latex document)
| tag=="table" = attrLatexEnv "longtable" (latexTabFormat htmlexp)
(showLatexTableContents htmlexp)
| tag=="tr" = let cells = map showLatexExp htmlexp
in concat (intersperse " & " cells) ++ "\\\\\n"
| tag=="td" = showLatexExps htmlexp
| tag=="br" = "\\par\n"
| tag=="hr" = "\\vspace{2ex}\\hrule\n"
| tag=="img" = "{" ++ maybe "{\\tt}" specialchars2tex
(findHtmlAttr "alt" attrs)
++ "}"
| tag=="input" && maybe "" id (findHtmlAttr "type" attrs) == "hidden" = ""
| otherwise = "{\\tt<"++tag++">}" ++ showLatexExps htmlexp ++
"{\\tt"++tag++">}"
-- create latex environment of name "env" with content "content"
latexEnvironment :: String -> String -> String
latexEnvironment env content = attrLatexEnv env "" content
-- create latex environment of name "env" with content "content"
-- adding the parameters "attr"
attrLatexEnv :: String -> String -> String -> String
attrLatexEnv env attr content
= "\\begin{"++env++"}"++attr++"\n"
++content
++"\n\\end{"++env++"}\n"
-- yield the format of a table, e.g. {lll} from list of html rows.
-- for longtables we set the chunksize big enough
-- to avoid having to rerun latex for inaccurat tables.
latexTabFormat :: [HtmlExp] -> String
latexTabFormat rows = "{" ++ replicate breadth 'l' ++ "}"
++ "\\setcounter{LTchunksize}{"++show (length rows+5)++"}%"
where
breadth = foldl max 0 (map getBreadth rows)
-- retrieve the breadth of an Html row
getBreadth :: HtmlExp -> Int
getBreadth row = case row of
HtmlStruct "tr" _ tds -> length tds
_ -> error "getBreadth: no row given"
-- tranlate expressions inside tables
showLatexTableContents :: [HtmlExp] -> String
showLatexTableContents hexps = concatMap showLatexTableContent hexps
-- tranlate expressions inside tables
showLatexTableContent :: HtmlExp -> String
showLatexTableContent (HtmlText s) = "{" ++ specialchars2tex s ++ "}"
showLatexTableContent (HtmlStruct tag attrs htmlexp)
| tag=="html" = showLatexTableContents htmlexp
| tag=="head" = "" -- ignore header
| tag=="body" = showLatexTableContents htmlexp
| tag=="form" = showLatexTableContents htmlexp
| tag=="p" = showLatexTableContents htmlexp ++ "\\par\n"
| tag=="b" = "{\\bf " ++ showLatexTableContents htmlexp ++ "}"
| tag=="em" = "\\emph{" ++ showLatexTableContents htmlexp ++ "}"
| tag=="i" = "{\\it " ++ showLatexTableContents htmlexp ++ "}"
| tag=="tt" = "{\\tt " ++ showLatexTableContents htmlexp ++ "}"
| tag=="font" = showLatexTableContents htmlexp -- ignore font changes
| tag=="address" = showLatexTableContents htmlexp
| tag=="blink" = showLatexTableContents htmlexp
| tag=="a" = showLatexTableContents htmlexp ++
-- add href attribute as footnote, if present:
maybe ""
(\url->"\\footnote{\\tt "++specialchars2tex url++"}\n")
(findHtmlAttr "href" attrs)
| tag=="tr" = let cells = map showLatexTableContent htmlexp
in concat (intersperse " & " cells) ++ "\\\\\n"
| tag=="td" = showLatexTableContents htmlexp
| tag=="br" = "\\par\n"
| tag=="hr" = "\\vspace{2ex}\\hrule\n"
| tag=="img" = "{" ++ maybe "{\\tt}" specialchars2tex
(findHtmlAttr "alt" attrs)
++ "}"
| tag=="input" && maybe "" id (findHtmlAttr "type" attrs) == "hidden" = ""
| otherwise = "{\\tt<"++tag++">}" ++ showLatexTableContents htmlexp ++
"{\\tt"++tag++">}"
-- find a specific tag field in a list of HTML attributes:
findHtmlAttr :: String -> [(String,String)] -> Maybe String
findHtmlAttr _ [] = Nothing
findHtmlAttr atag ((t,f):attrs) =
if atag==t then Just f
else findHtmlAttr atag attrs
--- Convert special characters into TeX representation, if necessary.
specialchars2tex :: String -> String
specialchars2tex = htmlSpecialChars2tex . escapeLaTeXSpecials
escapeLaTeXSpecials :: String -> String
escapeLaTeXSpecials [] = []
escapeLaTeXSpecials (c:cs)
| c=='^' = "{\\tt\\char94}" ++ escapeLaTeXSpecials cs
| c=='~' = "{\\tt\\char126}" ++ escapeLaTeXSpecials cs
| c=='\\' = "{\\textbackslash}" ++ escapeLaTeXSpecials cs
| c=='<' = "{\\tt\\char60}" ++ escapeLaTeXSpecials cs
| c=='>' = "{\\tt\\char62}" ++ escapeLaTeXSpecials cs
| c=='_' = "\\_" ++ escapeLaTeXSpecials cs
| c=='#' = "\\#" ++ escapeLaTeXSpecials cs
| c=='$' = "\\$" ++ escapeLaTeXSpecials cs
| c=='%' = "\\%" ++ escapeLaTeXSpecials cs
| c=='{' = "\\{" ++ escapeLaTeXSpecials cs
| c=='}' = "\\}" ++ escapeLaTeXSpecials cs
| otherwise = c : escapeLaTeXSpecials cs
--- Convert special HTML characters into their LaTeX representation,
--- if necessary.
htmlSpecialChars2tex :: String -> String
htmlSpecialChars2tex [] = []
htmlSpecialChars2tex (c:cs)
| c==chr 228 = "{\\\"a}" ++ htmlSpecialChars2tex cs
| c==chr 246 = "{\\\"o}" ++ htmlSpecialChars2tex cs
| c==chr 252 = "{\\\"u}" ++ htmlSpecialChars2tex cs
| c==chr 196 = "{\\\"A}" ++ htmlSpecialChars2tex cs
| c==chr 214 = "{\\\"O}" ++ htmlSpecialChars2tex cs
| c==chr 220 = "{\\\"U}" ++ htmlSpecialChars2tex cs
| c==chr 223 = "{\\ss}" ++ htmlSpecialChars2tex cs
| c=='&' = let (special,rest) = break (==';') cs
in if null rest
then "\\&" ++ htmlSpecialChars2tex special -- wrong format
else htmlspecial2tex special ++
htmlSpecialChars2tex (tail rest)
| otherwise = c : htmlSpecialChars2tex cs
htmlspecial2tex :: String -> String
htmlspecial2tex special
| special=="Auml" = "{\\\"A}"
| special=="Euml" = "{\\\"E}"
| special=="Iuml" = "{\\\"I}"
| special=="Ouml" = "{\\\"O}"
| special=="Uuml" = "{\\\"U}"
| special=="auml" = "{\\\"a}"
| special=="euml" = "{\\\"e}"
| special=="iuml" = "{\\\"\\i}"
| special=="ouml" = "{\\\"o}"
| special=="uuml" = "{\\\"u}"
| special=="szlig" = "{\\ss}"
| special=="Aacute" = "{\\\'A}"
| special=="Eacute" = "{\\\'E}"
| special=="Iacute" = "{\\\'I}"
| special=="Oacute" = "{\\\'O}"
| special=="Uacute" = "{\\\'U}"
| special=="aacute" = "{\\\'a}"
| special=="eacute" = "{\\\'e}"
| special=="iacute" = "{\\\'\\i}"
| special=="oacute" = "{\\\'o}"
| special=="uacute" = "{\\\'u}"
| special=="Agrave" = "{\\`A}"
| special=="Egrave" = "{\\`E}"
| special=="Igrave" = "{\\`I}"
| special=="Ograve" = "{\\`O}"
| special=="Ugrave" = "{\\`U}"
| special=="agrave" = "{\\`a}"
| special=="egrave" = "{\\`e}"
| special=="igrave" = "{\\`\\i}"
| special=="ograve" = "{\\`o}"
| special=="ugrave" = "{\\`u}"
| special=="Acirc" = "{\\^A}"
| special=="Ecirc" = "{\\^E}"
| special=="Icirc" = "{\\^I}"
| special=="Ocirc" = "{\\^O}"
| special=="Ucirc" = "{\\^U}"
| special=="acirc" = "{\\^a}"
| special=="ecirc" = "{\\^e}"
| special=="icirc" = "{\\^\\i}"
| special=="ocirc" = "{\\^o}"
| special=="ucirc" = "{\\^u}"
| special=="Oslash" = "{\\O}"
| special=="oslash" = "{\\o}"
| special=="amp" = "{\\&}"
| special=="ntilde" = "{\\~n}"
| special=="otilde" = "{\\~o}"
| special=="ccedil" = "{\\c{c}}"
| special=="nbsp" = "~"
| special=="quot" = "\""
| special=="lt" = "{$<$}"
| special=="gt" = "{$>$}"
| otherwise = "\\&"++special++";"
------------------------------------------------------------------------------
--- Transforms HTML expressions into a string representation of a complete
--- LaTeX document.
showLatexDoc :: [HtmlExp] -> String
showLatexDoc htmlexps = showLatexDocs [htmlexps]
--- Transforms HTML expressions into a string representation of a complete
--- LaTeX document.
--- The variable "packages" holds the packages to add to the latex document
--- e.g. "ngerman"
showLatexDocWithPackages :: [HtmlExp] -> [String] -> String
showLatexDocWithPackages hexps packages
= showLatexDocsWithPackages [hexps] packages
--- Transforms a list of HTML expressions into a string representation
--- of a complete LaTeX document where each list entry appears
--- on a separate page.
showLatexDocs :: [[HtmlExp]] -> String
showLatexDocs htmlexps_list = showLatexDocsWithPackages htmlexps_list []
--- Transforms a list of HTML expressions into a string representation
--- of a complete LaTeX document where each list entry appears
--- on a separate page.
--- The variable "packages" holds the packages to add to the latex document
--- (e.g., "ngerman").
showLatexDocsWithPackages :: [[HtmlExp]] -> [String] -> String
showLatexDocsWithPackages htmlexps_list packages =
"\\documentclass[12pt]{article}\n"++
concatMap (\p->"\\usepackage{"++p++"}\n") packages++
-- Package longtable is added by default.
"\\usepackage{longtable}"++
"\\nonstopmode\n"++
"\\setlength{\\topmargin}{ -1.5cm}\n"++
"\\setlength{\\oddsidemargin}{0.0cm}\n"++
"\\setlength{\\evensidemargin}{0.0cm}\n"++
"\\setlength{\\marginparwidth}{0.0cm}\n"++
"\\setlength{\\marginparsep}{0.0cm}\n"++
"\\setlength{\\textwidth}{16.5cm}\n"++
"\\setlength{\\textheight}{24.0cm}\n"++
"\\pagestyle{empty}\n"++
"\\begin{document}\n\\sloppy\n"++
"\\addtolength{\\baselineskip}{0.0ex}\n"++
"\\setlength{\\parindent}{0.0ex}\n"++
"\\addtolength{\\parskip}{0.5ex}\n"++
concat (intersperse "\\newpage\n" (map showLatexExps htmlexps_list))++
"\\end{document}\n"
--- show german latex document
germanLatexDoc :: [HtmlExp] -> String
germanLatexDoc hexps = showLatexDocWithPackages hexps ["ngerman"]
------------------------------------------------------------------------------
--- Execute an HTML form in "interactive" mode.
intForm :: IO HtmlForm -> IO ()
intForm = intFormMain "" "" "" "" False ""
--intcgi = intFormMain "http://localhost/~mh/" "/home/mh/public_html/" "" "fwdcgienv.cgi" False ""
--- Execute an HTML form in "interactive" mode with various parameters.
--- @param baseurl - the base URL where this script is accessible for clients
--- @param basecgi - the base directory in the local file system where
--- this script should stored for execution
--- @param reldir - the relative path added to baseurl and basecgi
--- @param cginame - the name of the executable cgi script
--- @param forever - True if the interactive execution should not be terminated
--- when the final web page (without a handler) is shown
--- @param urlparam - the URL parameter for the initial call to the cgi script
--- @param hformact - IO action returning the HTML form
intFormMain :: String -> String -> String -> String ->
Bool -> String -> IO HtmlForm -> IO ()
intFormMain baseurl basecgi reldir cginame forever urlparam hformact = do
pid <- getPID
user <- getEnviron "USER"
home <- getHomeDirectory
let portname = "intcgi_" ++ show pid
socket <- listenOn portname
let cgiprogname = if null cginame then "cgitest_"++show pid++".cgi"
else cginame
url = (if null baseurl then "http://localhost/~"++user else baseurl)
++ "/" ++ reldir ++ "/" ++ cgiprogname
cgifile = (if null basecgi then home++"/public_html/" else basecgi++"/")++
(if null reldir then "" else reldir ++"/") ++ cgiprogname
cgikey = url++" 42"
installShowCgiEnvScript portname cgifile
setEnviron "QUERY_STRING" urlparam
time <- getClockTime
intFormInEnv url cgikey hformact hformact [] (initialServerState time)
forever socket
system ("rm "++cgifile) >> done
intFormInEnv :: String -> String -> IO HtmlForm -> IO HtmlForm
-> [(String,String)] -> ServerState -> Bool -> Socket -> IO ()
intFormInEnv url cgikey initform hformact cenv state forever socket = do
if null cenv then putStrLn ">>> Start initial web form..." else done
cform <- hformact
let (cookiestring,hform) = extractCookies cform
(htmlstring,evhs) <- showHtmlFormInEnv url "" (extendForm hform)
(getMaxFieldNr cenv + 1)
nstate <- storeEnvHandlers state
(formWithMultipleHandlers hform)
(encodeKey cgikey)
(filter (\ (t,_) -> t/="DEFAULT" && take 6 t /= "EVENT_") cenv)
evhs
showHtmlStringInBrowser (cookiestring++htmlstring)
if forever || formWithHandlers hform
then do putStrLn ">>> Waiting for web page submission..."
(_,hdl) <- socketAccept socket
mbmsg <- readCgiServerMsg hdl
maybe (intFormInEnv url cgikey initform hformact
cenv state forever socket)
(intFormProceed nstate hdl)
mbmsg
else putStrLn ">>> Final web page reached"
where
intFormProceed nstate hdl (CgiSubmit scriptenv newcenv) = do
hPutStrLn hdl answerTxt
hClose hdl
mapIO_ (\ (var,val) -> setEnviron var val) scriptenv
if null newcenv -- call to initial script?
then intFormInEnv url cgikey initform initform [] nstate forever socket
else do
(rstate,mfe) <- getNextFormAndCgiEnv nstate cgikey newcenv
maybe (putStrLn "ERROR: no submission handler")
(\ (ioform,env) -> intFormInEnv url cgikey initform ioform
env rstate forever socket)
mfe
answerTxt = "Content-Type: text/html\n\n" ++
showHtmlExp (italic [htxt "Waiting for next web form..."])
extendForm orgform =
orgform `addFormParam` HeadInclude (HtmlStruct "base" [("href",url)] [])
-- has an HTML form event handlers?
formWithHandlers :: HtmlForm -> Bool
formWithHandlers (HtmlForm _ _ hexps) = hasHandlers hexps
where
hasHandlers :: [HtmlExp] -> Bool
hasHandlers [] = False
hasHandlers (HtmlText _ : hes) = hasHandlers hes
hasHandlers (HtmlStruct _ _ hes1 : hes2) =
hasHandlers hes1 || hasHandlers hes2
hasHandlers (HtmlCRef he _ : hes) = hasHandlers [he] || hasHandlers hes
hasHandlers (HtmlEvent _ _ : _) = True
hasHandlers (AjaxEvent _ _ : _) = True
hasHandlers (AjaxEvent2 _ _ _ _ : _) = True
--- Shows a string in HTML format in a browser.
showHtmlStringInBrowser :: String -> IO ()
showHtmlStringInBrowser htmlstring = do
pid <- getPID
let htmlfilename = "tmpcgiform_" ++ show pid ++ ".html"
writeFile htmlfilename htmlstring
system ("remote-netscape file:`pwd`/"++htmlfilename)
done
-- install web script that forward user inputs:
installShowCgiEnvScript :: String -> String -> IO ()
installShowCgiEnvScript portname cgifile = do
putStrLn ">>> Installing web script..."
putStrLn $ "for port name: "++portname
writeFile cgifile $ "#!/bin/sh\n"++
installDir++"/www/submitform \""++portname++"\"\n"
system ("chmod 755 "++cgifile)
done
------------------------------------------------------------------------------
-- The server for each dynamic web page manages the event handlers used in
-- dynamic web pages on the server side.
-- Each event handler is stored on the server side with a unique key.
-- Only this key is sent in the actual web page to the client.
-- Event handlers are only valid for a particular time period
-- specified by eventHandlerExpiration
, i.e., after that time
-- event handlers will be deleted.
-- The structure of the internal state of the server:
-- Argument 1: Time when the server has been started.
-- Argument 2: Current index for numbering new events
-- Argument 3: Next date when cleanup is necessary
-- Argument 4: The current event handlers
-- (index,expiration date,cgikey,env,handler,groupindex)
-- where groupindex is Nothing for handlers with multiple use
-- and (Just gk) if the handlers should be deleted together
-- with all other handlers having the same groupindex
-- (usually, belonging to the same page)
type ServerState =
(ClockTime, Int, ClockTime,
[(Int,ClockTime,String,([(String,String)],HtmlHandler),Maybe Int)])
--- Creates a new state for a server started at some time.
initialServerState :: ClockTime -> ServerState
initialServerState ctime = (ctime, 0, nextCleanup ctime, [])
--- Is the list of event handlers of a server state empty?
isServerStateWithoutHandlers :: ServerState -> Bool
isServerStateWithoutHandlers (_,_,_,evhandlers) = null evhandlers
--- Gets a string describing the load of the server process.
--- If the server is "busy" it cannot accept further requests
--- for initial web pages.
getServerLoad :: ServerState -> IO String
getServerLoad (stime,maxkey,_,evs) = do
ctime <- getClockTime
let busy = maxkey>500
|| (compareClockTime ctime (addMinutes 30 stime) == GT)
|| null evs -- since a server without handlers will be terminated
return (if busy then "busy" else "ready")
--- Gets a string describing the status of the server process.
getServerStatus :: ServerState -> IO String
getServerStatus state@(stime,maxkey,_,evs) = do
busy <- getServerLoad state
lstime <- toCalendarTime stime
return $ "Status: " ++ busy ++ ", Maxkey: "++show maxkey ++ ", #Handlers: " ++
show (length evs) ++ ", Start time: " ++
calendarTimeToString lstime ++ "\n"
--- Shows the group key of a handler as a string.
showGroupKey :: Maybe Int -> String
showGroupKey Nothing = "multiple use"
showGroupKey (Just gk) = "group " ++ show gk
--- Stores a list of new event handlers for a given cgi program and
--- the corresponding arguments with a new key.
--- The second argument is True if the event handlers should only be used once.
storeEnvHandlers :: ServerState -> Bool -> String -> [(String,String)]
-> [(HtmlHandler,String)] -> IO ServerState
storeEnvHandlers ostate multipleuse cgikey env handlerkeys = do
time <- getClockTime
cstate <- cleanOldEventHandlers ostate
rannums <- getRandomSeed >>= return . drop 3 . nextInt
let nstate = generateEventServerMessages
rannums
(if multipleuse then Nothing else Just (keyOfState cstate))
(eventHandlerExpiration time)
cstate
handlerkeys
seq nstate done -- to ensure that handler keys are instantiated
return nstate
where
generateEventServerMessages _ _ _ state [] = state
generateEventServerMessages (rannum:rannums) groupkey expiredate state
((handler,hkey) : evhs)
| hkey =:= show (keyOfState state) ++ ' ':showQTerm (toUTCTime expiredate)
++ '_' : show rannum -- add random element to handler key string
= generateEventServerMessages
rannums
groupkey
expiredate
(storeNewEnvEventWithCgiKey groupkey expiredate state env handler)
evhs
keyOfState (_,key,_,_) = key
storeNewEnvEventWithCgiKey groupkey date (stime,maxkey,cleandate,ehs)
cenv info =
(stime,
if maxkey>30000 then 0 else maxkey+1, -- to avoid integer overflows
cleandate,
(maxkey,date,cgikey,(cenv,info),groupkey):ehs)
-- clean event handlers that are too old:
cleanOldEventHandlers :: ServerState -> IO ServerState
cleanOldEventHandlers state@(_,_,_,[]) = return state
cleanOldEventHandlers state@(stime,maxkey,cleandate,ehs@(_:_)) = do
ctime <- getClockTime
if compareClockTime ctime cleandate == LT
then return state
else do
let currentehs = filter (isNotExpired ctime) ehs
noehs = length ehs
nocurrentehs = length currentehs
if nocurrentehs < noehs
then do -- report cleanup numbers:
ltime <- toCalendarTime ctime
putErrLn $ calendarTimeToString ltime ++ ": cleanup " ++
"(number of handlers: old = "++ show noehs ++ " / " ++
"current = "++ show nocurrentehs ++ ")"
else done
return (stime,maxkey, nextCleanup ctime, currentehs)
where
isNotExpired time (_,etime,_,_,_) = compareClockTime time etime == LT
-- Retrieves a previously stored event handler for a cgi program.
-- Returns Nothing if the handler is no longer available, i.e., expired.
retrieveEnvHandlers :: ServerState -> String -> String
-> IO (ServerState,Maybe ([(String,String)],HtmlHandler))
retrieveEnvHandlers state cgikey skey =
let (numstring,datestring) = break (==' ') skey
dateps = readsQTerm datestring
num = tryReadNat (-1) numstring
in if null datestring || null dateps || num < 0
then return (state,Nothing)
else let (newstate,info) =
getEnvEventWithCgiKey state num (fst (head dateps))
in seq newstate (return (newstate, info))
-- the "seq"s are put here and below to enfore the evaluation of the
-- new state in order to avoid space leaks with old, unused handlers
where
getEnvEventWithCgiKey oldstate@(stime,maxkey,cleandate,ehs) key date =
maybe (oldstate,Nothing)
(\ (evhdlr,groupkey) ->
maybe (oldstate, Just evhdlr)
(\gk -> let newehs = deleteEv gk ehs
in seq newehs ((stime,maxkey,cleandate,newehs),
Just evhdlr))
groupkey )
(searchEv ehs)
where
-- search event handler
searchEv [] = Nothing
searchEv ((n,t,c,i,gk):es) =
if key==n && date == toUTCTime t
then if c==cgikey then Just (i,gk) else Nothing
else searchEv es
-- delete event handlers of the same group
deleteEv _ [] = []
deleteEv groupkey (ev@(_,_,_,_,Nothing):es) =
let des = deleteEv groupkey es in seq des (ev : des)
deleteEv groupkey (ev@(_,_,_,_,Just gk):es) =
if groupkey==gk
then deleteEvInGroup groupkey es
else let des = deleteEv groupkey es in seq des (ev : des)
deleteEvInGroup _ [] = []
deleteEvInGroup _ (ev@(_,_,_,_,Nothing):es) = ev : es
deleteEvInGroup groupkey (ev@(_,_,_,_,Just gk):es) =
if groupkey==gk
then deleteEvInGroup groupkey es
else ev : es -- a new group has started so we stop the deletion
-- Define for a given date a new date when the event handler expires.
eventHandlerExpiration :: ClockTime -> ClockTime
eventHandlerExpiration = addHours 1
--eventHandlerExpiration = addMinutes 1
-- Define for a given date a new date when the next cleanup of event handlers
-- should be done.
nextCleanup :: ClockTime -> ClockTime
nextCleanup = addMinutes 5
---------------------------------------------------------------------------