{- In this file we incrementally construct a formatter that takes a structured document, consisting of a hierachy of sections into html: In a sequence of steps we add: 1 generation of simple html, in which headers are just formatted in bold 2 proper header tags are being generated, depending of the nesting level of the section 3 we provide each header with a prefix indicating its position 4 a table of contents is computed and may be inserted at any place 5 next/prev links are added The exercise is to add the AG code for next/prev links as described above. -} imports { import System } { main = do { args <- getArgs ; case args of ["1"] -> putStrLn test1 ["2"] -> putStrLn test1 ["3"] -> putStrLn test1 ["4"] -> putStrLn test4 ["5"] -> putStrLn test5 _ -> putStrLn "usage: .. " } } --------------------------------------------------------------------- {- 0 FP preliminaries Since we will be dealing with many strings when generating html, we have decided to take a more efficient representation for strings, that makes concatenation linear on the length of the string being constructed. -} { type Str = String -> String emptyStr :: Str emptyStr = id strToString :: Str -> String strToString s = s [] tok :: Char -> Str tok t = (t:) space :: Str space = tok ' ' toks :: String -> Str toks s = (s++) mkTag :: String -> Str -> Str -> Str mkTag tag attrs elem = tok '<' . toks tag . attrs . tok '>' . elem . toks "' } --------------------------------------------------------------------- --------------------------------------------------------------------- {- 1 A Simple document formatter We start by defining the non-terminals and productions that describe the global document structure. In order to cope with future extensions we also add a root production. -} --------------------------------------------------------------------- DATA Root | Root doc:Doc DATA Doc | Section title:String body:Docs | Pcdata string:String TYPE Docs = [ Doc ] -- a document has a single synthesized attribute -- if no rules are given we concatenate (indirectly via .) the synthesized html-attributes -- of the children, and if no child has an html attribute we take the empty string. ATTR Root Doc Docs [ | | html USE {.} {id}: Str ] -- the semantic rules that tell how to compute the html attribute -- the omitted rules are automatically generated by the copy-rule mechanism SEM Doc | Section lhs.html = mkTag "P" emptyStr (mkTag "B" emptyStr (toks @title)) . @body.html | Pcdata lhs.html = mkTag "P" emptyStr (toks @string) -- a sample document { doc1 = Root_Root ( Doc_Section "Intro" [ -- Doc_Toc, Doc_Section "Section" [ Doc_Pcdata "paragraph" , Doc_Pcdata "paragraph" ] , Doc_Section "Section" [ Doc_Pcdata "paragraph" , Doc_Pcdata "paragraph" ] ] ) test1 = strToString (sem_Root doc1) } --------------------------------------------------------------------- {- 2 Generating proper section headers In order to keep track of the level at which a specific Section is occuring we declare an inhertited attribute level. Since we will be chaining the definition of the header in the future we compute its html in a local attribute called head -} --------------------------------------------------------------------- ATTR Doc Docs [ level:Int | | ] SEM Doc | Section loc .head = mkTag ("H"++show @lhs.level) emptyStr (toks @title) body .level = @lhs.level + 1 lhs .html := @head . @body.html -- initialisation of the level at the root SEM Root | Root doc .level = 1 --------------------------------------------------------------------- {- 3 Constructing the proper prefix In order to add the proper information about nesting levels to the headings we add two attributes: - context which contains the string describing the indices of the outer levels - a chained attribute that is used to keep track of the count at the current level -} --------------------------------------------------------------------- ATTR Doc Docs [ context:String | count : Int | ] SEM Doc | Section body .count = 1 .context = strToString @prefix lhs .count = @lhs.count + 1 loc .prefix = (if null @lhs.context then id else toks (@lhs.context ++ ".") ) . toks (show @lhs.count) loc .head := mkTag ("H"++show @lhs.level) emptyStr (@prefix . space . toks @title) -- initialisation of the level at the root SEM Root | Root doc .count = 1 .context = "" --------------------------------------------------------------------- {- 4 Constructing a table of contents A table of content is a nested bulleted html list gathered in attribute toclines, and passed back as attribut toc. The -} --------------------------------------------------------------------- DATA Doc | Toc ATTR Doc Docs [ toc: Str | | toclines USE {.} {emptyStr}: Str] SEM Doc | Section lhs .toclines = mkTag "LI" emptyStr (mkTag ("A") (space . toks "HREF=#" . @prefix) (@prefix . space . toks @title)) . mkTag "UL" emptyStr @body.toclines loc .anchor = mkTag "A" (space . toks "NAME=" . @prefix) emptyStr lhs .html := @anchor . @head . @body.html | Toc lhs .html = @lhs.toc -- initialisation of the level at the root SEM Root | Root doc .toc = @doc.toclines { doc4 = Root_Root ( Doc_Section "Intro" [ Doc_Toc , Doc_Section "Section" [ Doc_Pcdata "paragraph" , Doc_Pcdata "paragraph" ] , Doc_Section "Section" [ Doc_Pcdata "paragraph" , Doc_Pcdata "paragraph" ] ] ) test4 = strToString (sem_Root doc4) } { doc5 = Root_Root ( Doc_Section "Intro" [ Doc_Toc , Doc_Section "Section" [ Doc_Pcdata "paragraph" , Doc_Section "Subsection" [ Doc_Pcdata "paragraph" , Doc_Pcdata "paragraph" ] , Doc_Pcdata "paragraph" , Doc_Section "Subsection" [ Doc_Pcdata "paragraph" , Doc_Pcdata "paragraph" ] ] , Doc_Section "Section" [ Doc_Pcdata "paragraph" , Doc_Pcdata "paragraph" ] , Doc_Toc ] ) test5 = strToString (sem_Root doc5) }