]> gitweb @ CieloNegro.org - Rakka.git/blobdiff - Rakka/Wiki/Engine.hs
Resurrection from bitrot
[Rakka.git] / Rakka / Wiki / Engine.hs
index 72effb3ed2414817ae7a4d09e6062082a657d06d..02e987cd20f7ed92c6eb7521f3cd2d2e877c6f3f 100644 (file)
@@ -1,23 +1,38 @@
+{-# LANGUAGE
+    Arrows
+  , OverloadedStrings
+  , ScopedTypeVariables
+  , TypeOperators
+  , UnicodeSyntax
+  #-}
 module Rakka.Wiki.Engine
     ( InterpTable
     , makeMainXHTML
     , makeSubXHTML
-    , makeDraft
     , makePreviewXHTML
+    , makePageLinkList
+    , makeDraft
     )
     where
-
-import qualified Codec.Binary.Base64 as B64
+import Control.Applicative
+import Control.Arrow
+import Control.Arrow.ArrowIO
+import Control.Arrow.ArrowList
+import Control.Arrow.Unicode
+import Control.Monad.Unicode
 import qualified Codec.Binary.UTF8.String as UTF8
-import           Control.Arrow
-import           Control.Arrow.ArrowIO
-import           Control.Arrow.ArrowList
 import qualified Data.ByteString.Lazy as Lazy
+import qualified Data.ByteString.Lazy.Char8 as L8 hiding (ByteString)
 import           Data.Map (Map)
 import qualified Data.Map as M
 import           Data.Maybe
+import Data.Monoid.Unicode
+import Data.Text (Text)
+import qualified Data.Text as T
 import           Network.HTTP.Lucu
 import           Network.URI
+import           OpenSSL.EVP.Base64
+import Prelude.Unicode
 import           Rakka.Page
 import           Rakka.Storage
 import           Rakka.SystemConfig
@@ -28,45 +43,43 @@ import           Rakka.Wiki.Formatter
 import           Rakka.Wiki.Interpreter
 import           Text.HyperEstraier hiding (getText)
 import           Text.ParserCombinators.Parsec
-import           Text.XML.HXT.Arrow.XmlArrow hiding (err)
-import           Text.XML.HXT.Arrow.XmlNodeSet
-import           Text.XML.HXT.DOM.TypeDefs
-
+import Text.XML.HXT.Arrow.XmlArrow hiding (err)
+import Text.XML.HXT.DOM.TypeDefs
+import Text.XML.HXT.XPath
 
-type InterpTable = Map String Interpreter
+type InterpTable = Map Text Interpreter
 
-
-wikifyPage :: (ArrowXml a, ArrowChoice a) => InterpTable -> a XmlTree WikiPage
+wikifyPage ∷ (ArrowXml (⇝), ArrowChoice (⇝)) ⇒ InterpTable → XmlTree ⇝ WikiPage
 wikifyPage interpTable
     = proc tree
-    -> do pType      <- getXPathTreesInDoc "/page/@type/text()" >>> getText >>> arr read -< tree
-          textData   <- maybeA (getXPathTreesInDoc "/page/textData/text()"   >>> getText) -< tree
-          base64Data <- maybeA (getXPathTreesInDoc "/page/binaryData/text()" >>> getText) -< tree
-
-          let dataURI = fmap (binToURI pType) base64Data
-
-          case pType of
-            MIMEType "text" "x-rakka" _
-                -> case parse (wikiPage $ cmdTypeOf interpTable) "" (fromJust textData) of
-                     Left err -> wikifyParseError -< err
-                     Right xs -> returnA -< xs
-
-            MIMEType "image" _ _
-                -- <img src="data:image/png;base64,..." />
-                -> returnA -< [ Paragraph [Image (Left $ fromJust dataURI) Nothing] ]
-
-            _   -> if isJust dataURI then
-                       -- <a href="data:application/zip;base64,...">
-                       --   application/zip
-                       -- </a>
-                       returnA -< [ Paragraph [ Anchor
-                                                [("href", show dataURI)]
-                                                [Text (show pType)]
-                                              ]
-                                  ]
-                   else
-                       -- pre
-                       returnA -< [ Preformatted [Text $ fromJust textData] ]
+    → do pType      ← getXPathTreesInDoc "/page/@type/text()" ⋙ getText ⋙ arr read   ⤙ tree
+         textData   ← maybeA (getXPathTreesInDoc "/page/textData/text()"   ⋙ getText) ⤙ tree
+         base64Data ← maybeA (getXPathTreesInDoc "/page/binaryData/text()" ⋙ getText) ⤙ tree
+
+         let dataURI = binToURI pType <$> base64Data
+
+         case pType of
+           MIMEType "text" "x-rakka" _
+                case parse (wikiPage $ cmdTypeOf interpTable) "" (fromJust textData) of
+                    Left err → wikifyParseError ⤙ err
+                    Right xs → returnA ⤙ xs
+
+           MIMEType "image" _ _
+               -- <img src="data:image/png;base64,..." />
+               → returnA ⤙ [ Paragraph [Image (Left $ fromJust dataURI) Nothing] ]
+
+           _   → if isJust dataURI then
+                     -- <a href="data:application/zip;base64,...">
+                     --   application/zip
+                     -- </a>
+                     returnA ⤙ [ Paragraph [ Anchor
+                                               [("href", T.pack $ show dataURI)]
+                                               [Text (T.pack $ show pType)]
+                                           ]
+                               ]
+                 else
+                     -- pre
+                     returnA ⤙ [ Preformatted [Text ∘ T.pack $ fromJust textData] ]
     where
       binToURI :: MIMEType -> String -> URI
       binToURI pType base64Data
@@ -82,69 +95,68 @@ wikifyPage interpTable
           | otherwise        = x : stripWhiteSpace xs
 
 
-wikifyBin :: (ArrowXml a, ArrowChoice a) => InterpTable -> a (MIMEType, Lazy.ByteString) WikiPage
+wikifyBin :: (ArrowXml (⇝), ArrowChoice (⇝)) ⇒ InterpTable → (MIMEType, Lazy.ByteString) ⇝ WikiPage
 wikifyBin interpTable
     = proc (pType, pBin)
-    -> do let text    = UTF8.decode $ Lazy.unpack pBin
-              dataURI = binToURI pType pBin
-
-          case pType of
-            MIMEType "text" "x-rakka" _
-                -> case parse (wikiPage $ cmdTypeOf interpTable) "" text of
-                     Left err -> wikifyParseError -< err
-                     Right xs -> returnA -< xs
-
-            MIMEType "image" _ _
-                -- <img src="data:image/png;base64,..." />
-                -> returnA -< [ Paragraph [Image (Left dataURI) Nothing] ]
-
-            
-            _
-                -- <a href="data:application/zip;base64,...">
-                --   application/zip (19372 bytes)
-                -- </a>
-                -> returnA -< [ Paragraph [ Anchor
-                                            [("href", show dataURI)]
-                                            [Text (show pType ++
-                                                   " (" ++
-                                                   show (Lazy.length pBin) ++
-                                                   " bytes)")]
-                                          ]
-                              ]
+    → do let text    = UTF8.decode $ Lazy.unpack pBin
+             dataURI = binToURI pType pBin
+
+         case pType of
+           MIMEType "text" "x-rakka" _
+               -> case parse (wikiPage $ cmdTypeOf interpTable) "" text of
+                    Left err -> wikifyParseError -< err
+                    Right xs -> returnA -< xs
+
+           MIMEType "image" _ _
+               -- <img src="data:image/png;base64,..." />
+               -> returnA -< [ Paragraph [Image (Left dataURI) Nothing] ]
+
+           _   -- <a href="data:application/zip;base64,...">
+               --   application/zip (19372 bytes)
+               -- </a>
+               -> returnA -< [ Paragraph [ Anchor
+                                           [("href", T.pack $ show dataURI)]
+                                           [Text (T.concat [ T.pack $ show pType
+                                                           , "("
+                                                           , T.pack ∘ show $ Lazy.length pBin
+                                                           , " bytes)"
+                                                           ])]
+                                         ]
+                             ]
     where
       binToURI :: MIMEType -> Lazy.ByteString -> URI
       binToURI m b
           = nullURI {
               uriScheme = "data:"
-            , uriPath   = show m ++ ";base64," ++ B64.encode (Lazy.unpack b)
+            , uriPath   = show m ++ ";base64," ++ (L8.unpack $ encodeBase64LBS b)
             }
 
-
-cmdTypeOf :: InterpTable -> String -> Maybe CommandType
+cmdTypeOf ∷ Alternative f ⇒ InterpTable → Text → f CommandType
 cmdTypeOf interpTable name
-    = fmap commandType (M.lookup name interpTable)
-
-
-makeMainXHTML :: (ArrowXml a, ArrowChoice a, ArrowIO a) =>
-                 Storage
-              -> SystemConfig
-              -> InterpTable
-              -> a XmlTree XmlTree
+    = case M.lookup name interpTable of
+        Just t  → pure $ commandType t
+        Nothing → empty
+
+makeMainXHTML ∷ (ArrowXml (⇝), ArrowChoice (⇝), ArrowIO (⇝))
+              ⇒ Storage
+              → SystemConfig
+              → InterpTable
+              → XmlTree ⇝ XmlTree
 makeMainXHTML sto sysConf interpTable
     = proc tree
-    -> do BaseURI baseURI <- getSysConfA sysConf -< ()
-          wiki            <- wikifyPage interpTable -< tree
-          pName           <- getXPathTreesInDoc "/page/@name/text()" >>> getText -< tree
-          interpreted     <- interpretCommands sto sysConf interpTable
-                             -< (pName, Just tree, Just wiki, wiki)
-          formatWikiBlocks -< (baseURI, interpreted)
+    → do BaseURI baseURI ← getSysConfA sysConf    ⤙ ()
+         wiki            ← wikifyPage interpTable ⤙ tree
+         pName           ← getXPathTreesInDoc "/page/@name/text()" ⋙ getText ⤙ tree
+         interpreted     ← interpretCommands sto sysConf interpTable
+                           ⤙ (Just (T.pack pName), Just tree, Just wiki, wiki)
+         formatWikiBlocks ⤙ (baseURI, interpreted)
 
 
 makeSubXHTML :: (ArrowXml a, ArrowChoice a, ArrowIO a) =>
                 Storage
              -> SystemConfig
              -> InterpTable
-             -> a (PageName, Maybe XmlTree, XmlTree) XmlTree
+             -> a (Maybe PageName, Maybe XmlTree, XmlTree) XmlTree
 makeSubXHTML sto sysConf interpTable
     = proc (mainPageName, mainPage, subPage)
     -> do BaseURI baseURI <- getSysConfA sysConf -< ()
@@ -170,7 +182,7 @@ makePreviewXHTML sto sysConf interpTable
     -> do BaseURI baseURI <- getSysConfA sysConf -< ()
           wiki            <- wikifyBin interpTable -< (pageType, pageBin)
           interpreted     <- interpretCommands sto sysConf interpTable
-                             -< (name, Nothing, Just wiki, wiki)
+                             -< (Just name, Nothing, Just wiki, wiki)
           formatWikiBlocks -< (baseURI, interpreted)
 
 
@@ -178,7 +190,7 @@ interpretCommands :: (ArrowXml a, ArrowChoice a, ArrowIO a) =>
                      Storage
                   -> SystemConfig
                   -> InterpTable
-                  -> a (PageName, Maybe XmlTree, Maybe WikiPage, WikiPage) WikiPage
+                  -> a (Maybe PageName, Maybe XmlTree, Maybe WikiPage, WikiPage) WikiPage
 interpretCommands sto sysConf interpTable
     = proc (name, mainPage, mainWiki, targetWiki)
     -> let ctx = InterpreterContext {
@@ -222,117 +234,112 @@ interpretCommands sto sysConf interpTable
                desc' <- mapM (interpInline ctx) desc
                return (Definition term' desc')
 
-      interpBlockCommand :: InterpreterContext -> BlockCommand -> IO BlockElement
+      interpBlockCommand ∷ InterpreterContext → BlockCommand → IO BlockElement
       interpBlockCommand ctx cmd
           = case M.lookup (bCmdName cmd) interpTable of
               Nothing
-                  -> fail ("no such interpreter: " ++ bCmdName cmd)
+                  → fail ("no such interpreter: " ⊕ T.unpack (bCmdName cmd))
 
               Just interp
-                  -> bciInterpret interp ctx cmd
-                     >>=
-                     interpBlock ctx
+                   bciInterpret interp ctx cmd
+                    =
+                    interpBlock ctx
 
-      interpInlineCommand :: InterpreterContext -> InlineCommand -> IO InlineElement
+      interpInlineCommand ∷ InterpreterContext → InlineCommand → IO InlineElement
       interpInlineCommand ctx cmd
           = case M.lookup (iCmdName cmd) interpTable of
               Nothing
-                  -> fail ("no such interpreter: " ++ iCmdName cmd)
+                  → fail ("no such interpreter: " ⊕ T.unpack (iCmdName cmd))
 
               Just interp
-                  -> iciInterpret interp ctx cmd
-                     >>=
-                     interpInline ctx
+                  → iciInterpret interp ctx cmd ≫= interpInline ctx
 
-
-makeDraft :: (ArrowXml a, ArrowChoice a, ArrowIO a) => InterpTable -> a XmlTree Document
+makeDraft ∷ ∀(⇝). (ArrowXml (⇝), ArrowChoice (⇝), ArrowIO (⇝)) ⇒ InterpTable → XmlTree ⇝ Document
 makeDraft interpTable
-    = proc tree ->
-      do redir <- maybeA (getXPathTreesInDoc "/page/@redirect") -< tree
+    = proc tree 
+      do redir ← maybeA (getXPathTreesInDoc "/page/@redirect") ⤙ tree
          case redir of
-           Nothing -> makeEntityDraft   -< tree
-           Just _  -> makeRedirectDraft -< tree
+           Nothing → makeEntityDraft   ⤙ tree
+           Just _  → makeRedirectDraft ⤙ tree
     where
-      makeEntityDraft :: (ArrowXml a, ArrowChoice a, ArrowIO a) => a XmlTree Document
+      makeEntityDraft ∷ XmlTree ⇝ Document
       makeEntityDraft 
-          = proc tree ->
-            do doc <- arrIO0 newDocument -< ()
+          = proc tree 
+            do doc ← arrIO0 newDocument ⤙ ()
          
-               pName     <- getXPathTreesInDoc "/page/@name/text()"         >>> getText -< tree
-               pType     <- getXPathTreesInDoc "/page/@type/text()"         >>> getText -< tree
-               pLastMod  <- getXPathTreesInDoc "/page/@lastModified/text()" >>> getText -< tree
-               pIsLocked <- getXPathTreesInDoc "/page/@isLocked/text()"     >>> getText -< tree
-               pIsBoring <- getXPathTreesInDoc "/page/@isBoring/text()"     >>> getText -< tree
-               pIsBinary <- getXPathTreesInDoc "/page/@isBinary/text()"     >>> getText -< tree
-               pRevision <- getXPathTreesInDoc "/page/@revision/text()"     >>> getText -< tree
-               pLang     <- maybeA (getXPathTreesInDoc "/page/@lang/text()"     >>> getText) -< tree
-               pFileName <- maybeA (getXPathTreesInDoc "/page/@fileName/text()" >>> getText) -< tree
-               pIsTheme  <- maybeA (getXPathTreesInDoc "/page/@isTheme/text()"  >>> getText) -< tree
-               pIsFeed   <- maybeA (getXPathTreesInDoc "/page/@isFeed/text()"   >>> getText) -< tree
-               pSummary  <- maybeA (getXPathTreesInDoc "/page/summary/text()"   >>> getText) -< tree
-
-               arrIO2 setURI                               -< (doc, Just $ mkRakkaURI pName)
-               arrIO2 (flip setAttribute "@title"        ) -< (doc, Just pName)
-               arrIO2 (flip setAttribute "@type"         ) -< (doc, Just pType)
-               arrIO2 (flip setAttribute "@mdate"        ) -< (doc, Just pLastMod)
-               arrIO2 (flip setAttribute "@lang"         ) -< (doc, pLang)
-               arrIO2 (flip setAttribute "rakka:fileName") -< (doc, pFileName)
-               arrIO2 (flip setAttribute "rakka:isLocked") -< (doc, Just pIsLocked)
-               arrIO2 (flip setAttribute "rakka:isBoring") -< (doc, Just pIsBoring)
-               arrIO2 (flip setAttribute "rakka:isBinary") -< (doc, Just pIsBinary)
-               arrIO2 (flip setAttribute "rakka:revision") -< (doc, Just pRevision)
-               arrIO2 (flip setAttribute "rakka:summary" ) -< (doc, pSummary)
-
-               arrIO2 addHiddenText -< (doc, pName)
+               pName     ← getXPathTreesInDoc "/page/@name/text()"         ⋙ getText ⤙ tree
+               pType     ← getXPathTreesInDoc "/page/@type/text()"         ⋙ getText ⤙ tree
+               pLastMod  ← getXPathTreesInDoc "/page/@lastModified/text()" ⋙ getText ⤙ tree
+               pIsLocked ← getXPathTreesInDoc "/page/@isLocked/text()"     ⋙ getText ⤙ tree
+               pIsBinary ← getXPathTreesInDoc "/page/@isBinary/text()"     ⋙ getText ⤙ tree
+               pRevision ← getXPathTreesInDoc "/page/@revision/text()"     ⋙ getText ⤙ tree
+               pLang     ← maybeA (getXPathTreesInDoc "/page/@lang/text()"     ⋙ getText) ⤙ tree
+               pIsTheme  ← maybeA (getXPathTreesInDoc "/page/@isTheme/text()"  ⋙ getText) ⤙ tree
+               pIsFeed   ← maybeA (getXPathTreesInDoc "/page/@isFeed/text()"   ⋙ getText) ⤙ tree
+               pSummary  ← maybeA (getXPathTreesInDoc "/page/summary/text()"   ⋙ getText) ⤙ tree
+
+               arrIO2 setURI                               ⤙ (doc, Just ∘ mkRakkaURI $ T.pack pName    )
+               arrIO2 (flip setAttribute "@title"        ) ⤙ (doc, Just              $ T.pack pName    )
+               arrIO2 (flip setAttribute "@type"         ) ⤙ (doc, Just              $ T.pack pType    )
+               arrIO2 (flip setAttribute "@mdate"        ) ⤙ (doc, Just              $ T.pack pLastMod )
+               arrIO2 (flip setAttribute "@lang"         ) ⤙ (doc, T.pack <$> pLang)
+               arrIO2 (flip setAttribute "rakka:isLocked") ⤙ (doc, Just              $ T.pack pIsLocked)
+               arrIO2 (flip setAttribute "rakka:isBinary") ⤙ (doc, Just              $ T.pack pIsBinary)
+               arrIO2 (flip setAttribute "rakka:revision") ⤙ (doc, Just              $ T.pack pRevision)
+               arrIO2 (flip setAttribute "rakka:summary" ) ⤙ (doc, T.pack <$> pSummary)
+
+               arrIO2 addHiddenText ⤙ (doc, T.pack pName)
 
                case pSummary of
-                 Just s  -> arrIO2 addHiddenText -< (doc, s)
-                 Nothing -> returnA -< ()
+                 Just s  → arrIO2 addHiddenText ⤙ (doc, T.pack s)
+                 Nothing → returnA ⤙ ()
 
                -- otherLang はリンク先ページ名を hidden text で入れる。
-               otherLangs <- listA (getXPathTreesInDoc "/page/otherLang/link/@page/text()" >>> getText) -< tree
+               otherLangs ← listA (getXPathTreesInDoc "/page/otherLang/link/@page/text()" ⋙ getText) ⤙ tree
                listA ( (arr fst &&& arrL snd)
-                       >>>
+                       ⋙
                        arrIO2 addHiddenText
-                       >>>
+                       ⋙
                        none
-                     ) -< (doc, otherLangs)
+                     ) ⤙ (doc, T.pack <$> otherLangs)
 
                case read pType of
                  MIMEType "text" "css" _
-                     -> arrIO2 (flip setAttribute "rakka:isTheme") -< (doc, pIsTheme)
+                     → arrIO2 (flip setAttribute "rakka:isTheme") ⤙ (doc, T.pack <$> pIsTheme)
            
                  MIMEType "text" "x-rakka" _
-                   -- wikify して興味のある部分を addText する。
-                   -> do arrIO2 (flip setAttribute "rakka:isFeed") -< (doc, pIsFeed)
-                         wiki <- wikifyPage interpTable -< tree
-                         arrIO2 (mapM_ . addBlockText) -< (doc, wiki)
+                     -- wikify して興味のある部分を addText する。
+                     → do arrIO2 (flip setAttribute "rakka:isFeed") ⤙ (doc, T.pack <$> pIsFeed)
+                          wiki ← wikifyPage interpTable ⤙ tree
+                          arrIO2 (mapM_ ∘ addBlockText) ⤙ (doc, wiki)
 
                  MIMEType _ _ _
-                     -> returnA -< ()
+                     → returnA ⤙ ()
 
-               returnA -< doc
+               returnA  doc
 
-      makeRedirectDraft :: (ArrowXml a, ArrowChoice a, ArrowIO a) => a XmlTree Document
+      makeRedirectDraft ∷ XmlTree ⇝ Document
       makeRedirectDraft
-          = proc tree ->
-            do doc <- arrIO0 newDocument -< ()
+          = proc tree 
+            do doc ← arrIO0 newDocument ⤙ ()
 
-               pName     <- getXPathTreesInDoc "/page/@name/text()"         >>> getText -< tree
-               pRedir    <- getXPathTreesInDoc "/page/@redirect/text()"     >>> getText -< tree
-               pRevision <- getXPathTreesInDoc "/page/@revision/text()"     >>> getText -< tree
-               pLastMod  <- getXPathTreesInDoc "/page/@lastModified/text()" >>> getText -< tree
+               pName     ← getXPathTreesInDoc "/page/@name/text()"         ⋙ getText ⤙ tree
+               pRedir    ← getXPathTreesInDoc "/page/@redirect/text()"     ⋙ getText ⤙ tree
+               pIsLocked ← getXPathTreesInDoc "/page/@isLocked/text()"     ⋙ getText ⤙ tree
+               pRevision ← getXPathTreesInDoc "/page/@revision/text()"     ⋙ getText ⤙ tree
+               pLastMod  ← getXPathTreesInDoc "/page/@lastModified/text()" ⋙ getText ⤙ tree
 
-               arrIO2 setURI                               -< (doc, Just $ mkRakkaURI pName)
-               arrIO2 (flip setAttribute "@title"        ) -< (doc, Just pName)
+               arrIO2 setURI                               -< (doc, Just ∘ mkRakkaURI $ T.pack pName      )
+               arrIO2 (flip setAttribute "@title"        ) -< (doc, Just              $ T.pack pName      )
                arrIO2 (flip setAttribute "@type"         ) -< (doc, Just "application/x-rakka-redirection")
-               arrIO2 (flip setAttribute "@mdate"        ) -< (doc, Just pLastMod)
-               arrIO2 (flip setAttribute "rakka:revision") -< (doc, Just pRevision)
+               arrIO2 (flip setAttribute "@mdate"        ) -< (doc, Just              $ T.pack pLastMod   )
+               arrIO2 (flip setAttribute "rakka:isLocked") -< (doc, Just              $ T.pack pIsLocked  )
+               arrIO2 (flip setAttribute "rakka:revision") -< (doc, Just              $ T.pack pRevision  )
 
                -- リダイレクト先ページ名はテキストとして入れる
-               arrIO2 addText -< (doc, pRedir)
+               arrIO2 addText ⤙ (doc, T.pack pRedir)
 
-               returnA -< doc
+               returnA  doc
 
       addElemText :: Document -> Element -> IO ()
       addElemText doc (Block  b) = addBlockText  doc b
@@ -349,23 +356,23 @@ makeDraft interpTable
       addBlockText _    EmptyBlock            = return ()
       addBlockText doc (BlockCmd bcmd)        = addBlockCmdText doc bcmd
 
-      addInlineText :: Document -> InlineElement -> IO ()
+      addInlineText ∷ Document → InlineElement → IO ()
       addInlineText doc (Text text)                       = addText doc text
       addInlineText doc (Italic inlines)                  = mapM_ (addInlineText doc) inlines
       addInlineText doc (Bold inlines)                    = mapM_ (addInlineText doc) inlines
       addInlineText doc (ObjectLink page Nothing)         = addText doc page
       addInlineText doc (ObjectLink page (Just text))     = addHiddenText doc page
-                                                            >> addText doc text
-      addInlineText doc (PageLink page fragm Nothing)     = addText doc (fromMaybe "" page ++ fromMaybe "" fragm)
-      addInlineText doc (PageLink page fragm (Just text)) = addHiddenText doc (fromMaybe "" page ++ fromMaybe "" fragm)
-                                                            >> addText doc text
-      addInlineText doc (ExternalLink uri Nothing)        = addText doc (uriToString id uri "")
-      addInlineText doc (ExternalLink uri (Just text))    = addHiddenText doc (uriToString id uri "")
-                                                            >> addText doc text
+                                                            *> addText    doc text
+      addInlineText doc (PageLink page fragm Nothing)     = addText       doc (fromMaybe (∅) page ⊕ maybe (∅) (T.cons '#') fragm)
+      addInlineText doc (PageLink page fragm (Just text)) = addHiddenText doc (fromMaybe (∅) page ⊕ maybe (∅) (T.cons '#') fragm)
+                                                            *> addText    doc text
+      addInlineText doc (ExternalLink uri Nothing)        = addText       doc (T.pack $ uriToString id uri "")
+      addInlineText doc (ExternalLink uri (Just text))    = addHiddenText doc (T.pack $ uriToString id uri "")
+                                                            *> addText    doc text
       addInlineText _   (LineBreak _)                     = return ()
       addInlineText doc (Span _ inlines)                  = mapM_ (addInlineText doc) inlines
       addInlineText doc (Image src alt)                   = do case src of
-                                                                 Left  uri  -> addHiddenText doc (uriToString id uri "")
+                                                                 Left  uri  -> addHiddenText doc (T.pack $ uriToString id uri "")
                                                                  Right page -> addHiddenText doc page
                                                                case alt of
                                                                  Just text -> addHiddenText doc text
@@ -390,7 +397,48 @@ makeDraft interpTable
       addInlineCmdText doc (InlineCommand _ _ inlines) = mapM_ (addInlineText doc) inlines
 
 
-wikifyParseError :: Arrow a => a ParseError WikiPage
+makePageLinkList ∷ (ArrowXml (⇝), ArrowChoice (⇝), ArrowIO (⇝))
+                 ⇒ Storage
+                 → SystemConfig
+                 → InterpTable
+                 → XmlTree ⇝ [PageName]
+makePageLinkList sto sysConf interpTable
+    = proc tree
+    → do wiki        ← wikifyPage interpTable ⤙ tree
+         pName       ← getXPathTreesInDoc "/page/@name/text()" ⋙ getText ⤙ tree
+         interpreted ← interpretCommands sto sysConf interpTable
+                       ⤙ (Just (T.pack pName), Just tree, Just wiki, wiki)
+         returnA ⤙ concatMap extractFromBlock interpreted
+    where
+      extractFromElem :: Element -> [PageName]
+      extractFromElem (Block  b) = extractFromBlock  b
+      extractFromElem (Inline i) = extractFromInline i
+
+      extractFromBlock :: BlockElement -> [PageName]
+      extractFromBlock (List _ items)         = concatMap extractFromListItem items
+      extractFromBlock (DefinitionList defs)  = concatMap extractFromDefinition defs
+      extractFromBlock (Preformatted inlines) = concatMap extractFromInline inlines
+      extractFromBlock (Paragraph inlines)    = concatMap extractFromInline inlines
+      extractFromBlock (Div _ elems)          = concatMap extractFromElem elems
+      extractFromBlock _                      = []
+
+      extractFromInline :: InlineElement -> [PageName]
+      extractFromInline (Italic inlines)           = concatMap extractFromInline inlines
+      extractFromInline (Bold inlines)             = concatMap extractFromInline inlines
+      extractFromInline (Span _ inlines)           = concatMap extractFromInline inlines
+      extractFromInline (PageLink (Just name) _ _) = [name]
+      extractFromInline _                          = []
+
+      extractFromListItem :: ListItem -> [PageName]
+      extractFromListItem = concatMap extractFromElem
+
+      extractFromDefinition :: Definition -> [PageName]
+      extractFromDefinition (Definition term desc)
+          = concatMap extractFromInline term
+            ++
+            concatMap extractFromInline desc
+
+wikifyParseError ∷ Arrow (⇝) ⇒ ParseError ⇝ WikiPage
 wikifyParseError = proc err
-                 -> returnA -< [Div [("class", "error")]
-                                [ Block (Preformatted [Text (show err)]) ]]
+                  returnA -< [Div [("class", "error")]
+                                [ Block (Preformatted [Text (T.pack $ show err)]) ]]