------------------------------------------------------------------------------
--- 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 October 2016
--- @category web
------------------------------------------------------------------------------
{-# 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 NamedSocket
import List
import Profile
import Random (getRandomSeed, nextInt)
import ReadNumeric (readNat, readHex)
import ReadShowTerm (showQTerm, readsQTerm)
import System
import Time
--import Unsafe(showAnyQExpression) -- to show status of cgi server
import Json
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
--- 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 :: [(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
pinfos <- getProcessInfos
return $ "Status: " ++ busy ++ ", Maxkey: "++show maxkey ++ ", #Handlers: " ++
show (length evs) ++ ", Start time: " ++
calendarTimeToString lstime ++ "\n" ++
showMemInfo pinfos
--- 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
---------------------------------------------------------------------------