/* NSC -- new Scala compiler
* Copyright 2007-2013 LAMP/EPFL
* @author David Bernard, Manohar Jonnalagedda
*/
package scala
package tools
package nsc
package doc
package html
import base._
import base.comment._
import model._
import scala.reflect.internal.Reporter
import scala.xml.NodeSeq
import scala.xml.Elem
import scala.xml.dtd.DocType
import scala.collection._
import java.io.Writer
/** An html page that is part of a Scaladoc site.
* @author David Bernard
* @author Gilles Dubochet */
abstract class HtmlPage extends Page { thisPage =>
/** The title of this page. */
protected def title: String
/** ScalaDoc reporter for error handling */
protected def docletReporter: Reporter
/** The page description */
protected def description: String =
// unless overwritten, will display the title in a spaced format, keeping - and .
title.replaceAll("[^a-zA-Z0-9\\.\\-]+", " ").replaceAll("\\-+", " - ").replaceAll(" +", " ")
/** The page keywords */
protected def keywords: String =
// unless overwritten, same as description, minus the " - "
description.replaceAll(" - ", " ")
/** Additional header elements (links, scripts, meta tags, etc.) required for this page. */
protected def headers: NodeSeq
/** The body of this page. */
def body: NodeSeq
def writeFor(site: HtmlFactory) {
val doctype = DocType("html")
val html =
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>{ title }</title>
<meta name="description" content={ description }/>
<meta name="keywords" content={ keywords }/>
<meta http-equiv="content-type" content={ "text/html; charset=" + site.encoding }/>
{ headers }
</head>
{ body }
</html>
writeFile(site) { (w: Writer) =>
w.write(doctype.toString + "\n")
w.write(xml.Xhtml.toXhtml(html))
w.write('\n')
}
if (site.universe.settings.docRawOutput)
writeFile(site, ".raw") {
// we're only interested in the body, as this will go into the diff
_.write(body.text)
}
}
/** Transforms an optional comment into an styled HTML tree representing its body if it is defined, or into an empty
* node sequence if it is not. */
def commentToHtml(comment: Option[Comment]): NodeSeq =
(comment map (commentToHtml(_))) getOrElse NodeSeq.Empty
/** Transforms a comment into an styled HTML tree representing its body. */
def commentToHtml(comment: Comment): NodeSeq =
bodyToHtml(comment.body)
def bodyToHtml(body: Body): NodeSeq =
body.blocks flatMap (blockToHtml(_))
def blockToHtml(block: Block): NodeSeq = block match {
case Title(in, 1) => <h3>{ inlineToHtml(in) }</h3>
case Title(in, 2) => <h4>{ inlineToHtml(in) }</h4>
case Title(in, 3) => <h5>{ inlineToHtml(in) }</h5>
case Title(in, _) => <h6>{ inlineToHtml(in) }</h6>
case Paragraph(in) => <p>{ inlineToHtml(in) }</p>
case Code(data) =>
<pre>{ SyntaxHigh(data) }</pre> //<pre>{ scala.xml.Text(data) }</pre>
case UnorderedList(items) =>
<ul>{ listItemsToHtml(items) }</ul>
case OrderedList(items, listStyle) =>
<ol class={ listStyle }>{ listItemsToHtml(items) }</ol>
case DefinitionList(items) =>
<dl>{items map { case (t, d) => <dt>{ inlineToHtml(t) }</dt><dd>{ blockToHtml(d) }</dd> } }</dl>
case HorizontalRule() =>
<hr/>
}
def listItemsToHtml(items: Seq[Block]) =
items.foldLeft(xml.NodeSeq.Empty){ (xmlList, item) =>
item match {
case OrderedList(_, _) | UnorderedList(_) => // html requires sub ULs to be put into the last LI
xmlList.init ++ <li>{ xmlList.last.child ++ blockToHtml(item) }</li>
case Paragraph(inline) =>
xmlList :+ <li>{ inlineToHtml(inline) }</li> // LIs are blocks, no need to use Ps
case block =>
xmlList :+ <li>{ blockToHtml(block) }</li>
}
}
def inlineToHtml(inl: Inline): NodeSeq = inl match {
case Chain(items) => items flatMap (inlineToHtml(_))
case Italic(in) => <i>{ inlineToHtml(in) }</i>
case Bold(in) => <b>{ inlineToHtml(in) }</b>
case Underline(in) => <u>{ inlineToHtml(in) }</u>
case Superscript(in) => <sup>{ inlineToHtml(in) }</sup>
case Subscript(in) => <sub>{ inlineToHtml(in) }</sub>
case Link(raw, title) => <a href={ raw } target="_blank">{ inlineToHtml(title) }</a>
case Monospace(in) => <code>{ inlineToHtml(in) }</code>
case Text(text) => scala.xml.Text(text)
case Summary(in) => inlineToHtml(in)
case HtmlTag(tag) => scala.xml.Unparsed(tag)
case EntityLink(target, link) => linkToHtml(target, link, hasLinks = true)
}
def linkToHtml(text: Inline, link: LinkTo, hasLinks: Boolean) = link match {
case LinkToTpl(dtpl: TemplateEntity) =>
if (hasLinks)
<a href={ relativeLinkTo(dtpl) } class="extype" name={ dtpl.qualifiedName }>{ inlineToHtml(text) }</a>
else
<span class="extype" name={ dtpl.qualifiedName }>{ inlineToHtml(text) }</span>
case LinkToMember(mbr: MemberEntity, inTpl: TemplateEntity) =>
if (hasLinks)
<a href={ relativeLinkTo(inTpl) + "#" + mbr.signature } class="extmbr" name={ mbr.qualifiedName }>{ inlineToHtml(text) }</a>
else
<span class="extmbr" name={ mbr.qualifiedName }>{ inlineToHtml(text) }</span>
case Tooltip(tooltip) =>
<span class="extype" name={ tooltip }>{ inlineToHtml(text) }</span>
case LinkToExternal(name, url) =>
<a href={ url } class="extype" target="_top">{ inlineToHtml(text) }</a>
case _ =>
inlineToHtml(text)
}
def typeToHtml(tpes: List[model.TypeEntity], hasLinks: Boolean): NodeSeq = tpes match {
case Nil =>
NodeSeq.Empty
case List(tpe) =>
typeToHtml(tpe, hasLinks)
case tpe :: rest =>
typeToHtml(tpe, hasLinks) ++ scala.xml.Text(" with ") ++ typeToHtml(rest, hasLinks)
}
def typeToHtml(tpe: model.TypeEntity, hasLinks: Boolean): NodeSeq = {
val string = tpe.name
def toLinksOut(inPos: Int, starts: List[Int]): NodeSeq = {
if (starts.isEmpty && (inPos == string.length))
NodeSeq.Empty
else if (starts.isEmpty)
scala.xml.Text(string.slice(inPos, string.length))
else if (inPos == starts.head)
toLinksIn(inPos, starts)
else {
scala.xml.Text(string.slice(inPos, starts.head)) ++ toLinksIn(starts.head, starts)
}
}
def toLinksIn(inPos: Int, starts: List[Int]): NodeSeq = {
val (link, width) = tpe.refEntity(inPos)
val text = comment.Text(string.slice(inPos, inPos + width))
linkToHtml(text, link, hasLinks) ++ toLinksOut(inPos + width, starts.tail)
}
if (hasLinks)
toLinksOut(0, tpe.refEntity.keySet.toList)
else
scala.xml.Text(string)
}
def typesToHtml(tpess: List[model.TypeEntity], hasLinks: Boolean, sep: NodeSeq): NodeSeq = tpess match {
case Nil => NodeSeq.Empty
case tpe :: Nil => typeToHtml(tpe, hasLinks)
case tpe :: tpes => typeToHtml(tpe, hasLinks) ++ sep ++ typesToHtml(tpes, hasLinks, sep)
}
def hasPage(e: DocTemplateEntity) = {
e.isPackage || e.isTrait || e.isClass || e.isObject || e.isCaseClass
}
/** Returns the HTML code that represents the template in `tpl` as a hyperlinked name. */
def templateToHtml(tpl: TemplateEntity, name: String = null) = tpl match {
case dTpl: DocTemplateEntity =>
if (hasPage(dTpl)) {
<a href={ relativeLinkTo(dTpl) } class="extype" name={ dTpl.qualifiedName }>{ if (name eq null) dTpl.name else name }</a>
} else {
scala.xml.Text(if (name eq null) dTpl.name else name)
}
case ndTpl: NoDocTemplate =>
scala.xml.Text(if (name eq null) ndTpl.name else name)
}
/** Returns the HTML code that represents the templates in `tpls` as a list of hyperlinked names. */
def templatesToHtml(tplss: List[TemplateEntity], sep: NodeSeq): NodeSeq = tplss match {
case Nil => NodeSeq.Empty
case tpl :: Nil => templateToHtml(tpl)
case tpl :: tpls => templateToHtml(tpl) ++ sep ++ templatesToHtml(tpls, sep)
}
object Image extends Enumeration {
val Trait, Class, Type, Object, Package = Value
}
def permalink(template: Entity, isSelf: Boolean = true): Elem =
<span class="permalink">
<a href={ memberToUrl(template, isSelf) } title="Permalink">
<i class="material-icons"></i>
</a>
</span>
def docEntityImageClass(tpl: DocTemplateEntity): String =
tpl.kind + tpl.companion.fold("")("-companion-" + _.kind)
def docEntityKindToCompanionTitle(ety: DocTemplateEntity, baseString: String = "See companion") =
ety.companion match{
case Some(companion) =>
s"$baseString${
if(companion.isObject) " object"
else if(companion.isTrait) " trait"
else if(companion.isClass) " class"
else ""
}"
case None => baseString
}
def companionAndPackage(tpl: DocTemplateEntity): NodeSeq =
<span class="morelinks">{
tpl.companion match {
case Some(companionTpl) =>
val objClassTrait =
if (companionTpl.isObject) s"object ${tpl.name}"
else if (companionTpl.isTrait) s"trait ${companionTpl.name}"
else s"class ${companionTpl.name}"
<div>
Companion <a href={relativeLinkTo(companionTpl)} title={docEntityKindToCompanionTitle(tpl)}>{objClassTrait}</a>
</div>
case None => NodeSeq.Empty
}
}</span>
private def memberToUrl(template: Entity, isSelf: Boolean = true): String = {
val (signature: Option[String], containingTemplate: TemplateEntity) = template match {
case dte: DocTemplateEntity if (!isSelf) => (Some(dte.signature), dte.inTemplate)
case dte: DocTemplateEntity => (None, dte)
case me: MemberEntity => (Some(me.signature), me.inTemplate)
case tpl => (None, tpl)
}
val templatePath = templateToPath(containingTemplate)
val url = "../" * (templatePath.size - 1) + templatePath.reverse.mkString("/")
url + signature.map("#" + _).getOrElse("")
}
}