packages feed

cpsa-2.2.0: src/CPSA/Graph/ExpandedView.hs

-- Generates an expanded view of CPSA output as a compound document
-- that contains SVG within XHTML.

-- Copyright (c) 2009 The MITRE Corporation
--
-- This program is free software: you can redistribute it and/or
-- modify it under the terms of the BSD License as published by the
-- University of California.

module CPSA.Graph.ExpandedView (expandedView) where

import qualified Data.Set as S
import Data.List (intersperse)
import System.IO
import CPSA.Lib.CPSA
import CPSA.Graph.XMLOutput
import CPSA.Graph.Config
import CPSA.Graph.SVG
import CPSA.Graph.Loader
import CPSA.Graph.Preskeleton
import CPSA.Graph.Tree

type Printer a = Int -> Int -> SExpr a -> String

expandedView :: Handle -> Config -> Int -> [SExpr Pos] -> [Preskel] -> IO ()
expandedView h conf margin cmts ps =
    do
      hPutList h (header (scripts conf) cmts ps)
      let pp = printer conf
      comments h margin pp cmts
      let f = forest ps
      case f of
        [t] ->
            tdrawer h conf margin pp False t
        _ ->
            do
              toc h f
              mapM_ (tdrawer h conf margin pp True) f
      hPutList h closer
      hClose h

header :: Bool -> [SExpr Pos] -> [Preskel] -> [String]
header False cmts ps =
    ["<html xmlns=\"http://www.w3.org/1999/xhtml\">",
     "<head>",
     " <meta http-equiv=\"content-type\"" ++
     " content=\"application/xhtml+xml; charset=UTF-8\" />",
     " <title>" ++ title cmts ps ++ "</title>",
     "</head>",
     "<body>"]
header True cmts ps =
    ["<html xmlns=\"http://www.w3.org/1999/xhtml\">",
     "<head>",
     " <meta http-equiv=\"content-type\"" ++
     " content=\"application/xhtml+xml; charset=UTF-8\" />",
     " <title>" ++ title cmts ps ++ "</title>",
     "  <script>"]
    ++ sliders ++
    ["  </script>",
     "</head>",
     "<body onload=\"init()\">"]

-- Find title in a herald form, but if absent use a protocol name.
title :: [SExpr Pos] -> [Preskel] -> String
title [] ps = protocolTitle ps
title ((L _ (S _ "herald" : S _ title : _)) : _) _ = title
title ((L _ (S _ "herald" : Q _ title : _)) : _) _ = title
title (_ : cmts) ps = title cmts ps

protocolTitle :: [Preskel] -> String
protocolTitle [] = "CPSA"
protocolTitle (k : _) = protocol k

comments :: Handle -> Int -> Printer Pos -> [SExpr Pos] -> IO ()
comments h margin pp cmts =
    do
      hPutStrLn h ""
      let xs = concat $ intersperse "\n" $ map (pp margin indent) cmts
      hPutStrLn h $ show $ mc "pre" [] xs

closer :: [String]
closer =
    ["", "</body>", "</html>"]

hPutList :: Handle -> [String] -> IO ()
hPutList h xs = mapM_ (hPutStrLn h) xs

-- Generates a list of trees within the document when there are more
-- than one.
toc :: Handle -> Forest -> IO ()
toc h f =
    do
      hPutStrLn h ""
      hPutStr h $ "<p id=\"" ++ topid ++ "\">Trees:"
      mapM_ (anchor h treeid . label . vertex) f
      hPutStrLn h ".</p>"

topid :: String
topid = "top"

anchor :: Handle -> (Int -> String) -> Int -> IO ()
anchor h id n =
    hPutStr h $ " <a href=\"#" ++ id n ++ "\">" ++ show n ++ "</a>"

-- Generates an SVG document root and puts in into a div element.
-- When scripting is enabled, it places all elements into a g element
-- that is used as the target of scaling actions.
docRoot :: Config -> Float -> Float -> [Element] -> Element
docRoot conf w h es =
    ec "div" dattrs [ec "svg" attrs elems]
    where
      attrs = [("width", showL w ++ units conf),
               ("height", showL h ++ units conf),
               ("xmlns", "http://www.w3.org/2000/svg"),
               ("xmlns:xlink", "http://www.w3.org/1999/xlink"),
               ("version", "1.1"),
               ("viewBox", viewbox),
               ("font-size", showL (font conf))]
      viewbox = "0 0 " ++ showL w ++ " " ++ showL h
      dattrs = if scripts conf then [("class", "scalable")] else []
      elems = if scripts conf then [ec "g" [] es] else es

-- Draws one tree
tdrawer :: Handle -> Config -> Int -> Printer Pos -> Bool -> Tree -> IO ()
tdrawer h conf margin pp toc t =
    do
      hPutStrLn h ""
      let id = label (vertex t)
      hPutStr h $ "<p id=\"" ++ treeid id ++ "\">Tree"
      case toc of
        True -> anchor h (\_ -> topid) id
        False -> hPutStr h $ " " ++ show id
      hPutStrLn h ".</p>"
      hPutStrLn h ""
      let (width, height, es) = tree conf t
      hPutStrLn h $ show $ docRoot conf width height $
                rect conf 0 0 width height : es
      hPutSExpr h margin pp (protSrc (vertex t))
      mapM_ (kdrawer h conf margin pp id) (collectPreskels t)

treeid :: Int -> String
treeid label = "t" ++ show label

-- Collects the preskeletons within a tree and sorts them by label.
collectPreskels :: Tree -> [Tree]
collectPreskels t =
    S.toAscList $ f S.empty t
    where
      f s t = foldl f (S.insert t s) (children t)

-- Draws one item in the tree--a preskeleton.
kdrawer :: Handle -> Config -> Int -> Printer Pos -> Int -> Tree -> IO ()
kdrawer h conf margin pp tid t =
    do
      hPutStrLn h ""
      let k = vertex t
      let id = label k
      hPutStr h $ "<p id=\"" ++ itemid id ++ "\">Item"
      anchor h (\_ -> treeid tid) id
      case parent k of
        Nothing -> return ()
        Just p ->
            do
              hPutStr h ", Parent:"
              anchor h itemid p
      titledList h "Child" "Children" $ map (label . vertex) (children t)
      titledList h "Seen Child" "Seen Children" $ seen k
      hPutStrLn h ".</p>"
      hPutStrLn h ""
      let (width, height, e) = kdraw conf 0 0 k
      hPutStrLn h $ show $ docRoot conf width height [defs conf, e]
      hPutSExpr h margin pp (preskelSrc k)

itemid :: Int -> String
itemid label = "i" ++ show label

-- Handle singular vs. plural.
titledList :: Handle -> String -> String -> [Int] -> IO ()
titledList _ _ _ [] =
    return ()
titledList h singular _ [id] =
    do
      hPutStr h $ ", " ++ singular ++ ":"
      anchor h itemid id
titledList h _ plural ls =
    do
      hPutStr h $ ", " ++ plural ++ ":"
      mapM_ (anchor h itemid) ls

hPutSExpr :: Handle -> Int -> Printer Pos -> SExpr Pos -> IO ()
hPutSExpr h margin pp sexpr =
    do
      hPutStrLn h ""
      hPutStrLn h $ show $ mc "pre" [] (pp margin indent sexpr)

-- S-expression pretty print parameters
indent :: Int
indent = 2

-- Encoded from src/sliders.js using src/js2hs
sliders :: [String]
sliders =
    ["<![CDATA[",
     "// Slider support for use with cpsagraph and option --scalers",
     "",
     "// Check to see if HTML5 sliders are available.",
     "function have_input_type(t) {",
     "    var ns = document.documentElement.namespaceURI;",
     "    var i = document.createElementNS(ns, \"input\");",
     "    i.setAttribute(\"type\", t);",
     "    return i.type !== \"text\";",
     "}",
     "",
     "var have_sliders = have_input_type(\"range\");",
     "",
     "// Create an element that controls scaling",
     "function scaler(ns) {",
     "    if (have_sliders) ",
     "        return slider(ns);",
     "    else",
     "        return selector(ns);",
     "}",
     "",
     "// Create an HTML5 slider for scaling",
     "function slider(ns) {",
     "    var i = document.createElementNS(ns, \"input\");",
     "    i.setAttribute(\"type\", \"range\");",
     "    i.setAttribute(\"min\", 0.2);",
     "    i.setAttribute(\"max\", 1.0);",
     "    i.setAttribute(\"step\", 0.2);",
     "    i.setAttribute(\"value\", 1.0);",
     "    i.addEventListener(\"change\", zoom, false);",
     "    return i;",
     "}",
     "",
     "// Create an old style select element for scaling",
     "function selector(ns) {",
     "    var s = document.createElementNS(ns, \"select\");",
     "    s.appendChild(option(ns, \"1.0\"));",
     "    s.appendChild(option(ns, \"0.8\"));",
     "    s.appendChild(option(ns, \"0.6\"));",
     "    s.appendChild(option(ns, \"0.4\"));",
     "    s.appendChild(option(ns, \"0.2\"));",
     "    s.addEventListener(\"change\", zoom, false);",
     "    return s;",
     "}",
     "",
     "// Create one option for use within a select element",
     "function option(ns, val) {",
     "    var o = document.createElementNS(ns, \"option\");",
     "    o.setAttribute(\"value\", val);",
     "    o.appendChild(document.createTextNode(val));",
     "    return o;",
     "}",
     "",
     "// At load time, insert browser appropriate scaler",
     "function init() {",
     "    var ns = document.documentElement.namespaceURI;",
     "    var nodes = document.getElementsByClassName(\"scalable\");",
     "    var i;",
     "    for (i = 0; i < nodes.length; i++) {",
     "        var n = nodes.item(i);",
     "        var b = document.createElementNS(ns, \"br\");",
     "        n.insertBefore(b, n.firstChild);",
     "        n.insertBefore(scaler(ns), n.firstChild);",
     "    }",
     "}",
     "",
     "// Find the g element within a near by svg element and apply a transform",
     "function zoom(evt) {",
     "    var g = evt.target.parentNode.lastElementChild.firstElementChild;",
     "    g.setAttribute(\"transform\", \"scale(\"+evt.target.value+\")\");",
     "}",
     "]]>"]