diff options
author | Gilles Dubochet <gilles.dubochet@epfl.ch> | 2010-05-15 18:44:18 +0000 |
---|---|---|
committer | Gilles Dubochet <gilles.dubochet@epfl.ch> | 2010-05-15 18:44:18 +0000 |
commit | bf82ecbcbeaa3a6975e360c2b616ce37938df80f (patch) | |
tree | 72d0b8e295f72db4814bf658f6a5c1f575941d21 | |
parent | f4420e7b13542f771c3ca80abfeab4dabf16b309 (diff) | |
download | scala-bf82ecbcbeaa3a6975e360c2b616ce37938df80f.tar.gz scala-bf82ecbcbeaa3a6975e360c2b616ce37938df80f.tar.bz2 scala-bf82ecbcbeaa3a6975e360c2b616ce37938df80f.zip |
[scaladoc] Closes #3428 (HTML markup supported ...
[scaladoc] Closes #3428 (HTML markup supported in Scaladoc). Improves
wiki-syntax parsing of lists. Improves stylesheet for headings and code
blocks in comments. Review by malayeri.
4 files changed, 146 insertions, 73 deletions
diff --git a/src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala b/src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala index 68d17c683f..66e2ba2260 100644 --- a/src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala +++ b/src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala @@ -128,10 +128,10 @@ abstract class HtmlPage { thisPage => body.blocks flatMap (blockToHtml(_)) def blockToHtml(block: Block): NodeSeq = block match { - case Title(in, 1) => <h1>{ inlineToHtml(in) }</h1> - case Title(in, 2) => <h2>{ inlineToHtml(in) }</h2> - case Title(in, 3) => <h3>{ inlineToHtml(in) }</h3> - case Title(in, _) => <h4>{ inlineToHtml(in) }</h4> + 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>{ xml.Text(data) }</pre> case UnorderedList(items) => @@ -168,6 +168,7 @@ abstract class HtmlPage { thisPage => case Monospace(text) => <code>{ xml.Text(text) }</code> case Text(text) => xml.Text(text) case Summary(in) => inlineToHtml(in) + case HtmlTag(tag) => xml.Unparsed(tag) } def typeToHtml(tpe: model.TypeEntity, hasLinks: Boolean): NodeSeq = { diff --git a/src/compiler/scala/tools/nsc/doc/html/resource/lib/template.css b/src/compiler/scala/tools/nsc/doc/html/resource/lib/template.css index 92de97f619..0c17d9fa2a 100644 --- a/src/compiler/scala/tools/nsc/doc/html/resource/lib/template.css +++ b/src/compiler/scala/tools/nsc/doc/html/resource/lib/template.css @@ -81,6 +81,7 @@ a:hover { } #comment { + padding-right: 8px; padding-left: 8px; } @@ -193,17 +194,56 @@ div.members > ol > li { .cmt {} .cmt p { - margin: 2px 0 2px 0; + margin-bottom: 0.4em; + margin-top: 0.4em; } -.cmt code { - font-family: monospace; +.cmt h3 { + margin-bottom: 1em; + margin-top: 1em; + display: block; + text-align: left; + font-weight: bold; + font-size: x-large; +} + +.cmt h4 { + margin-bottom: 0.6em; + margin-top: 0.6em; + display: block; + text-align: left; + font-weight: bold; + font-size: large; +} + +.cmt h5 { + margin-bottom: 0.4em; + margin-top: 0.4em; + display: block; + text-align: left; + font-weight: bold; +} + +.cmt h6 { + margin-bottom: 0.4em; + margin-top: 0.4em; + display: block; + text-align: left; + font-style: italic; } .cmt pre { + padding: 0.4em; + border-color: #ddd; + border-style: solid; + border-width: 1px; + margin-left: 0; + margin-bottom: 0.4em; + margin-right: 0; + margin-top: 0.4em; + background-color: #eee; display: block; font-family: monospace; - margin: 2px 0 2px 0; } .cmt ul { @@ -241,20 +281,24 @@ div.members > ol > li { display:list-item; } +.cmt code { + font-family: monospace; +} + .cmt a { - text-decoration: underline; + font-style: bold; } /* Comments structured layout */ p.comment { display: block; - margin-left: 8.7em; + margin-left: 8.7em; } p.shortcomment { display: block; - margin-left: 8.7em; + margin-left: 8.7em; cursor: help; } @@ -264,7 +308,7 @@ div.fullcomment { } #template div.fullcomment { - margin: 6px 0 6px 8.7em; + margin: 6px 0 6px 8.7em; } div.fullcomment .block { diff --git a/src/compiler/scala/tools/nsc/doc/model/comment/Body.scala b/src/compiler/scala/tools/nsc/doc/model/comment/Body.scala index 29e47fa578..255c61095e 100644 --- a/src/compiler/scala/tools/nsc/doc/model/comment/Body.scala +++ b/src/compiler/scala/tools/nsc/doc/model/comment/Body.scala @@ -67,6 +67,7 @@ final case class Link(target: String, title: Inline) extends Inline final case class EntityLink(target: TemplateEntity) extends Inline final case class Monospace(text: String) extends Inline final case class Text(text: String) extends Inline +final case class HtmlTag(data: String) extends Inline /** The summary of a comment, usually its first sentence. There must be exactly one summary per body. */ final case class Summary(text: Inline) extends Inline diff --git a/src/compiler/scala/tools/nsc/doc/model/comment/CommentFactory.scala b/src/compiler/scala/tools/nsc/doc/model/comment/CommentFactory.scala index 63b7b10859..475fbf584e 100644 --- a/src/compiler/scala/tools/nsc/doc/model/comment/CommentFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/model/comment/CommentFactory.scala @@ -57,21 +57,31 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory => new Regex("""(?:\s*\*\s?)?(.*)""") /** Dangerous HTML tags that should be replaced by something safer, such as wiki syntax, or that should be dropped. */ - protected val DangerousHtml = - new Regex("""<(/?(?:p|div|pre|ol|ul|li|h[1-6]|code))[^>]*>""") + protected val DangerousTags = + new Regex("""<(/?(div|ol|ul|li|h[1-6]|p))( [^>]*)?/?>|<!--.*-->""") /** Maps a dangerous HTML tag to a safe wiki replacement, or an empty string if it cannot be salvaged. */ - protected def htmlReplacement(mtch: Regex.Match): String = mtch.matched match { + protected def htmlReplacement(mtch: Regex.Match): String = mtch.group(1) match { case "p" | "div" => "\n\n" - case "h1" | "h2" | "h3" | "h4" | "h5" | "h6" => "\n= " - case "/h1" | "/h2" | "/h3" | "/h4" | "/h5" | "/h6" => " =\n" - case "pre" => "{{{" - case "/pre" => "}}}" + case "h1" => "\n= " + case "/h1" => " =\n" + case "h2" => "\n== " + case "/h2" => " ==\n" + case "h3" => "\n=== " + case "/h3" => " ===\n" + case "h4" | "h5" | "h6" => "\n==== " + case "/h4" | "/h5" | "/h6" => " ====\n" case "code" | "/code" => "`" - case "li" => "\n - " + case "li" => "\n * - " case _ => "" } + /** Safe HTML tags that can be kept. */ + protected val SafeTags = + new Regex("""(</?(abbr|acronym|address|area|a|bdo|big|blockquote|br|button|b|caption|code|cite|col|colgroup|dd|del|dfn|em|fieldset|form|hr|img|input|ins|i|kbd|label|legend|link|map|object|optgroup|option|param|pre|q|samp|select|small|span|strong|sub|sup|table|tbody|td|textarea|tfoot|th|thead|tr|tt|var)( [^>]*)?/?>)""") + + protected val safeTagMarker = '\u000E' + /** A Scaladoc tag not linked to a symbol. Returns the name of the tag, and the rest of the line. */ protected val SimpleTag = new Regex("""\s*@(\S+)\s+(.*)""") @@ -114,8 +124,12 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory => } } val strippedComment = comment.trim.stripPrefix("/*").stripSuffix("*/") - val safeComment = DangerousHtml.replaceAllIn(strippedComment, { htmlReplacement(_) }) - safeComment.lines.toList map (cleanLine(_)) + val safeComment = DangerousTags.replaceAllIn(strippedComment, { htmlReplacement(_) }) + val markedTagComment = + SafeTags.replaceAllIn(safeComment, { mtch => + java.util.regex.Matcher.quoteReplacement(safeTagMarker + mtch.matched + safeTagMarker) + }) + markedTagComment.lines.toList map (cleanLine(_)) } /** Parses a comment (in the form of a list of lines) to a Comment instance, recursively on lines. To do so, it @@ -251,17 +265,6 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory => var summaryParsed = false - /** listStyle ::= '-' spc | '1.' spc | 'I.' spc | 'i.' spc | 'A.' spc | 'a.' spc - * Characters used to build lists and their constructors */ - protected val listStyles = Map[String, (Seq[Block] => Block)]( // TODO Should this be defined at some list companion? - "- " -> ( UnorderedList(_) ), - "1. " -> ( OrderedList(_,"decimal") ), - "I. " -> ( OrderedList(_,"upperRoman") ), - "i. " -> ( OrderedList(_,"lowerRoman") ), - "A. " -> ( OrderedList(_,"upperAlpha") ), - "a. " -> ( OrderedList(_,"lowerAlpha") ) - ) - def document(): Body = { nextChar() val blocks = new mutable.ListBuffer[Block] @@ -287,9 +290,20 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory => } } + /** listStyle ::= '-' spc | '1.' spc | 'I.' spc | 'i.' spc | 'A.' spc | 'a.' spc + * Characters used to build lists and their constructors */ + protected val listStyles = Map[String, (Seq[Block] => Block)]( // TODO Should this be defined at some list companion? + "- " -> ( UnorderedList(_) ), + "1. " -> ( OrderedList(_,"decimal") ), + "I. " -> ( OrderedList(_,"upperRoman") ), + "i. " -> ( OrderedList(_,"lowerRoman") ), + "A. " -> ( OrderedList(_,"upperAlpha") ), + "a. " -> ( OrderedList(_,"lowerAlpha") ) + ) + /** Checks if the current line is formed with more than one space and one the listStyles */ def checkList = - countWhitespace > 0 && listStyles.keysIterator.indexWhere(checkSkipInitWhitespace(_)) >= 0 + (countWhitespace > 0) && (listStyles.keys exists { checkSkipInitWhitespace(_) }) /** {{{ * nListBlock ::= nLine { mListBlock } @@ -297,41 +311,37 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory => * }}} * Where n and m stand for the number of spaces. When m > n, a new list is nested. */ def listBlock: Block = { - /** consumes one line of a list block */ - def listLine(indentedListStyle: String): Block = { - // deals with mixed lists in the same nesting level by skipping it - if(!jump(indentedListStyle)) { // TODO show warning when jump is false - nextChar(); - nextChar() - } - val p = Paragraph(inline(check(Array(endOfLine)))) - blockEnded("end of list line ") - p - } - def listLevel(leftSide: String, listStyle: String, constructor: (Seq[Block] => Block)): Block = { - val blocks = mutable.ListBuffer.empty[Block] - val length = leftSide.length - val indentedListStyle = leftSide + listStyle - - var index = 1 - var line = listLine(indentedListStyle) - - while (index > -1) { - blocks += line - if (countWhitespace > length) { // nesting-in - blocks += listBlock // TODO is tailrec really needed here? - } - index = listStyles.keysIterator.indexWhere(x => check(leftSide)) - if (index > -1) { line = listLine(indentedListStyle) } + + /** Consumes one list item block and returns it, or None if the block is not a list or a different list. */ + def listLine(indent: Int, style: String): Option[Block] = + if (countWhitespace > indent && checkList) + Some(listBlock) + else if (countWhitespace != indent || !checkSkipInitWhitespace(style)) + None + else { + jumpWhitespace() + jump(style) + val p = Paragraph(inline(false)) + blockEnded("end of list line ") + Some(p) } - constructor(blocks) + /** Consumes all list item blocks (possibly with nested lists) of the same list and returns the list block. */ + def listLevel(indent: Int, style: String): Block = { + val lines = mutable.ListBuffer.empty[Block] + var line: Option[Block] = listLine(indent, style) + while (line.isDefined) { + lines += line.get + line = listLine(indent, style) + } + val constructor = listStyles(style) + constructor(lines) } - val indentation = countWhitespace - val indentStr = " " * indentation - val style = listStyles.keysIterator.find( x => check(indentStr + x) ).getOrElse(listStyles.keysIterator.next) - val constructor = listStyles(style) - listLevel(indentStr, style, constructor) + + val indent = countWhitespace + val style = (listStyles.keys find { checkSkipInitWhitespace(_) }).getOrElse(listStyles.keys.head) + listLevel(indent, style) + } def code(): Block = { @@ -388,7 +398,8 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory => def inline(isInlineEnd: => Boolean): Inline = { def inline0(): Inline = { - if (check("'''")) bold() + if (char == safeTagMarker) htmlTag() + else if (check("'''")) bold() else if (check("''")) italic() else if (check("`")) monospace() else if (check("__")) underline() @@ -396,7 +407,7 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory => else if (check(",,")) subscript() else if (check("[[")) link() else { - readUntil { check("''") || char == '`' || check("__") || char == '^' || check(",,") || check("[[") || isInlineEnd || checkParaEnded || char == endOfLine } + readUntil { char == safeTagMarker || check("''") || char == '`' || check("__") || char == '^' || check(",,") || check("[[") || isInlineEnd || checkParaEnded || char == endOfLine } Text(getRead()) } } @@ -424,6 +435,14 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory => } + def htmlTag(): Inline = { + jump(safeTagMarker) + readUntil(safeTagMarker) + if (char != endOfText) jump(safeTagMarker) + var read = getRead + HtmlTag(read) + } + def bold(): Inline = { jump("'''") val i = inline(check("'''")) @@ -531,7 +550,7 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory => checkSkipInitWhitespace(Array(endOfLine)) || checkSkipInitWhitespace(Array('=')) || checkSkipInitWhitespace(Array('{', '{', '{')) || - checkSkipInitWhitespace(Array(' ', '-', ' ')) || + checkList || checkSkipInitWhitespace(Array('\u003D')) } offset = poff @@ -607,10 +626,18 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory => /* JUMPERS */ - /** jumps all the characters in chars - * @return true only if the correct characters have been jumped - * consumes any matching characters - */ + /** jumps a character and consumes it + * @return true only if the correct character has been jumped */ + final def jump(ch: Char): Boolean = { + if (char == ch) { + nextChar() + true + } + else false + } + + /** jumps all the characters in chars, consuming them in the process. + * @return true only if the correct characters have been jumped */ final def jump(chars: Array[Char]): Boolean = { var index = 0 while (index < chars.length && char == chars(index) && char != endOfText) { |