package org.scala_tools.vscaladoc
import scala.tools.nsc._
import java.io.{File, FileWriter}
import java.net.{URI, URLEncoder}
//import scala.collection.jcl
//import scala.compat.Platform.{EOL => LINE_SEPARATOR}
import scala.xml.{NodeSeq, Text, Unparsed, Utility}
/**
* @author David Bernard
*/
trait HtmlPage {
/** to override */
def uri: URI
/** to override */
def title: String = Services.cfg.windowTitle
/** to override */
def header: Option[NodeSeq] = None
/** to override */
def body: Option[NodeSeq] = None
def relativize(that: URI) : String = Services.linkHelper.relativize(that, this.uri).getOrElse("#")
def relativize(that: String) : String = relativize(new URI(that))
def htmlize(comment: Option[ModelExtractor#Comment]) : NodeSeq = htmlize(comment, true)
//TODO group attr.tag with same value
def htmlize(comment: Option[ModelExtractor#Comment], splitDetails: Boolean) : NodeSeq = {
def listAttributes(c: ModelExtractor#Comment) = {
if (c.attributes.filter(_.body.trim.length > 0).isEmpty) {
NodeSeq.Empty
} else {
var last = ""
<dl>{c.attributes.sort(_.tag < _.tag).filter(_.body.trim.length > 0).map(attr => <xml:group>
{
if (last != attr.tag) {
last = attr.tag
<dt>{attr.tag}</dt>
} else {
NodeSeq.Empty
}
}
<dd><code>{attr.option}</code> - {Unparsed(attr.body)}</dd>
</xml:group>)
}</dl>
}
}
def display(c: ModelExtractor#Comment) = {
<div class="apiComments">
{Unparsed(c.body)}
{listAttributes(c)}
</div>
}
def displayWithDetails(c: ModelExtractor#Comment) = {
var detailsPos = c.body.indexOf('.')
if (c.body.startsWith("<p>")) detailsPos= c.body.indexOf("</p>") + 3
if (detailsPos == -1) {
detailsPos = c.body.length
}
val first = c.body.substring(0, detailsPos)
val details = if ((detailsPos+1) < c.body.length) c.body.substring(detailsPos+1).trim else ""
<div class="apiComments">
{Unparsed(first)}
{ if ((details.length > 0) || (c.attributes.filter(_.body.trim.length > 0).size > 0)) {
<xml:group>
<a href="javascript://" onclick="jQuery(this).next().toggle()" class="detailsBtn">[details]</a>
<div class="apiCommentsDetails">
{Unparsed(details)}
{listAttributes(c)}
</div>
</xml:group>
} else {
NodeSeq.Empty
}
}
</div>
}
comment match {
case Some(c: ModelExtractor#Comment) => {
if (splitDetails) {
displayWithDetails(c)
} else {
display(c)
}
}
case None => NodeSeq.Empty
}
}
val dtype = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">"
val encoding = Services.cfg.encodingString
val header0 =
<xml:group>
<title>{Text(title)}</title>
<meta http-equiv="content-type" content={"text/html; charset=" + encoding}/>
<meta name="generator" content={System.getProperty("doc.generator", "scaladoc (" + Services.cfg.versionString + ")")}/>
<script type="text/javascript" src={relativize("site:/jquery-1.2.3.js")}></script>
</xml:group>
def html =
<html>
<head>
{header0}
{header.getOrElse(NodeSeq.Empty)}
</head>
{body.getOrElse(NodeSeq.Empty)}
</html>
def save(rootDir: File) = {
val file = new File(rootDir, uri.getPath.substring(1))
val parent = file.getParentFile()
if (!parent.exists()) parent.mkdirs()
val writer = new FileWriter(file)
try {
writer.write(dtype)
writer.append(scala.compat.Platform.EOL)
writer.append(html.toString())
} finally {
writer.close()
}
}
}
class Page4Blank(filename: String) extends HtmlPage {
def uri = new URI("site:" + filename)
override def body = Some(<body/>)
}
class Page4Index(navFrame: HtmlPage, contentFrame: HtmlPage) extends HtmlPage {
def uri = new URI("site:/index.html")
override def body = Some(
<frameset cols="250px, *">
<frame src={relativize(navFrame.uri)} name="navFrame" scrolling="yes"/>
<frame src={relativize(contentFrame.uri)} name="contentFrame" scrolling="yes"/>
</frameset>
)
}
class Page4AllClasses(allPackages: Iterable[ModelExtractor#Package], allClasses: Iterable[ModelExtractor#ClassOrObject]) extends HtmlPage {
def uri = new URI("site:/all-classes.html")
override def title = "List of all classes and objects"
override def header = Some(
<xml:group>
<link rel="stylesheet" href={relativize("site:/all-classes.css")} type="text/css"/>
<script id="all-classes" src={relativize("site:/all-classes.js")}></script>
</xml:group>
)
override def body = Some(
<xml:group>
{filtersBody}
{classesBody}
</xml:group>
)
//TODO : bind onchange, onclick from js
private def filtersBody: NodeSeq = {
// <a id="filterAll" href="#" onclick="showAll()" title="All">[A]</a>
// <a id="filterNode" href="#" onclick="hideAll()" title="None">[N]</a>
<xml:group>
<h2>Filters</h2>
<div class="ctrl">
<select id="packagesFilter" multiple="true" size="6.5">
{allPackages.map(pkg => <option>{pkg.name}</option>)}
</select>
<div id="kindFilters">
<a id="filter_class" class="class" href="#" title="Class">Class<input type="checkbox" checked="true" id="filter_class_cb"/></a>
<a id="filter_trait" class="trait" href="#" title="Trait">Trait<input type="checkbox" checked="true" id="filter_trait_cb"/></a>
<a id="filter_object" class="object" href="#" title="Object">Object<input type="checkbox" checked="true" id="filter_object_cb"/></a>
</div>
<input id="nameFilter" type="text"/>
</div>
</xml:group>
}
// <a href={urlFor(cls)} target={contentFrame} title={cls.fullName('.')}>{cls.name}</a>
private def classesBody: NodeSeq = {
val namePlusMap = new scala.collection.mutable.HashMap[String, String]()
def compare(t1: ModelExtractor#ClassOrObject, t2: ModelExtractor#ClassOrObject) = {
if (t1.name.toLowerCase == t2.name.toLowerCase) {
val f1 = t1.fullName('.')
val f2 = t2.fullName('.')
if (f1 != f2) {
namePlusMap(f1) = f1.substring(0, f1.length-t1.name.length)
namePlusMap(f2) = f2.substring(0, f2.length-t2.name.length)
f1.toLowerCase < f2.toLowerCase
} else {
t1.kind < t2.kind
}
} else {
t1.name.toLowerCase < t2.name.toLowerCase
}
}
val classes = allClasses.toList.sort((t1, t2) => compare(t1,t2))
def css(cls: ModelExtractor#ClassOrObject) = cls.kind
<xml:group>
<h2>Classes</h2>
<ul id="classes">
{classes.map(cls => <li class={css(cls)} title={css(cls)} package={Services.modelHelper.packageFor(cls.sym).get.fullNameString('.')}>{Services.linkHelper.link(cls, this.uri, None, Some("contentFrame"))}{namePlusMap.get(cls.fullName('.')).map(" ("+_+")").getOrElse("")}</li>)}
</ul>
</xml:group>
}
}
//TODO manage next and prev (as meta link and navbar)
/*
<script id="shCore.js" src={relativize("site:/_highlighter/shCore.js")} language='javascript' ></script>
<script id="shBrushCSharp.js" src={relativize("site:/_highlighter/shBrushCSharp.js")} language='javascript' ></script>
<script id="shBrushJava.js" src={relativize("site:/_highlighter/shBrushJava.js")} language='javascript' ></script>
<script id="shBrushScala.js" src={relativize("site:/_highlighter/shBrushScala.js")} language='javascript' ></script>
<script id="shBrushShell.js" src={relativize("site:/_highlighter/shBrushShell.js")} language='javascript' ></script>
<script id="shBrushSql.js" src={relativize("site:/_highlighter/shBrushSql.js")} language='javascript' ></script>
<script id="shBrushXml.js" src={relativize("site:/_highlighter/shBrushXml.js")} language='javascript' ></script>
*/
abstract class ContentPage extends HtmlPage{
def surroundHeader(nodes: Option[NodeSeq]) = Some(
<xml:group>
<link rel="stylesheet" href={relativize("site:/content.css")} type="text/css"/>
<script id="content.js" src={relativize("site:/content.js")} language='javascript' ></script>
<link rel='stylesheet' href={relativize("site:/_highlighter/SyntaxHighlighter.css")} type='text/css'/>
<script id="shAll.js" src={relativize("site:/_highlighter/shAll.js")} language='javascript' ></script>
{nodes.getOrElse("")}
</xml:group>
)
def surroundBody(nodes: Option[NodeSeq]) = Some(
<xml:group>
<div class="header">{Services.cfg.pageHeader}</div>
<!-- ========= START OF TOP NAVBAR ======= -->
<a name="navbar_top"><!-- --></a>
{navBar}
<!-- ========= END OF TOP NAVBAR ========= -->
{nodes.getOrElse("")}
<!-- ======= START OF BOTTOM NAVBAR ====== -->
<a name="navbar_bottom"><!-- --></a>
{navBar}
<!-- ======== END OF BOTTOM NAVBAR ======= -->
{Services.cfg.pageFooter}
<script language='javascript'>
dp.SyntaxHighlighter.ClipboardSwf = '{relativize("site:/_highlighter/clipboard.swf")}';
dp.SyntaxHighlighter.HighlightAll('code');
</script>
</xml:group>
)
def navBar = {
<table border="0" width="100%" cellpadding="1" cellspacing="0" class="NavBar">
<tr>
<td class="NavBarCell1">{navBarCell1}</td>
<td class="NavBarCell2">{navBarCell2}</td>
<td class="NavBarCell3">{navBarCell3}</td>
</tr>
</table>
}
def navBarCell1 : NodeSeq = NodeSeq.Empty
def navBarCell2 : NodeSeq = {
<xml:group>
<a href={relativize("site:/index.html")} target="_top">FRAMES</a>
<a href={val path=uri.getPath; path.substring(path.lastIndexOf('/')+1)} target="_top">NO FRAMES</a>
</xml:group>
}
def navBarCell3 : NodeSeq = NodeSeq.Empty
}
class Page4Overview(allPackages: Iterable[ModelExtractor#Package]) extends ContentPage {
def uri = new URI("site:/overview.html")
override def title = super.title + " : Overview"
override def header = surroundHeader(None)
override def body = surroundBody(Some(
<xml:group>
{pageTitle}
{overviewComment}
{packages}
</xml:group>
))
private def pageTitle: NodeSeq = {<h1>{Services.cfg.overviewTitle}</h1>}
/**
* workaround because compiler doesn't read overview.html
*/
private def overviewComment: NodeSeq = <div>{Unparsed(Services.fileHelper.readTextFromSrcDir("overview.html").getOrElse(""))}</div>
private def packages = {
<div>
<h2>Packages</h2>
<dl>
{allPackages.map(
pkg => <xml:group>
<dt><a href={"javascript:selectPackage('" +pkg.name + "')"}>{pkg.name}</a></dt>
<dd>
{htmlize(pkg.decodeComment)}
{ //workaround because compiler doesn't read package.html
val path = pkg.fullName('/') + "/package.html"
Unparsed(Services.fileHelper.readTextFromSrcDir(path).getOrElse(""))
}
</dd>
</xml:group>)
}
</dl>
</div>
}
}