aboutsummaryrefslogtreecommitdiff
path: root/doc-tool/src/dotty/tools/dottydoc/model/comment/HtmlParsers.scala
blob: 1faa25f326c5e9525d8b12840f4b506d9a0b607d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
package dotty.tools
package dottydoc
package model
package comment

import dotc.core.Contexts.Context
import dotc.util.Positions._
import dotty.tools.dottydoc.util.syntax._
import util.MemberLookup

import com.vladsch.flexmark.ast.{ Node => MarkdownNode }
import com.vladsch.flexmark.html.HtmlRenderer
import com.vladsch.flexmark.parser.Parser
import com.vladsch.flexmark.util.sequence.CharSubSequence

object HtmlParsers {

  implicit class StringToMarkdown(val text: String) extends AnyVal {
    def toMarkdown(origin: Entity)(implicit ctx: Context): MarkdownNode = {
      import com.vladsch.flexmark.ast.{ Link, Visitor, VisitHandler, NodeVisitor }

      val inlineToHtml = InlineToHtml(origin)

      val node = Parser.builder(staticsite.Site.markdownOptions)
        .build.parse(text)

      def isOuter(url: String) =
        url.startsWith("http://") ||
        url.startsWith("https://") ||
        url.startsWith("ftp://") ||
        url.startsWith("ftps://")

      def isRelative(url: String) =
        url.startsWith("../") ||
        url.startsWith("./")

      val linkVisitor = new NodeVisitor(
        new VisitHandler(classOf[Link], new Visitor[Link] with MemberLookup {
          def queryToUrl(title: String, link: String) = makeEntityLink(origin, ctx.docbase.packages, Text(title), link).link match {
            case Tooltip(_) => "#"
            case LinkToExternal(_, url) => url
            case LinkToEntity(t: Entity) => t match {
              case e: Entity with Members => inlineToHtml.relativePath(t)
              case x => x.parent.fold("#") { xpar => inlineToHtml.relativePath(xpar) }
            }
          }

          override def visit(link: Link) = {
            val linkUrl = link.getUrl.toString
            if (!isOuter(linkUrl) && !isRelative(linkUrl))
              link.setUrl(CharSubSequence.of(queryToUrl(link.getTitle.toString, linkUrl)))
          }
        })
      )

      linkVisitor.visit(node)
      node
    }

    def toMarkdownString(origin: Entity)(implicit ctx: Context): String =
      toMarkdown(origin).show
  }

  implicit class MarkdownToHtml(val markdown: MarkdownNode) extends AnyVal {
    def show(implicit ctx: Context): String =
      HtmlRenderer.builder(staticsite.Site.markdownOptions).build().render(markdown)

    def shortenAndShow(implicit ctx: Context): String =
      (new MarkdownShortener).shorten(markdown).show
  }

  implicit class StringToWiki(val text: String) extends AnyVal {
    def toWiki(origin: Entity, packages: Map[String, Package], pos: Position): Body =
      new WikiParser(origin, packages, text, pos, origin.symbol).document()
  }

  implicit class BodyToHtml(val body: Body) extends AnyVal {
    def show(origin: Entity): String = {
      val inlineToHtml = InlineToHtml(origin)

      def bodyToHtml(body: Body): String =
        (body.blocks map blockToHtml).mkString

      def blockToHtml(block: Block): String = block match {
        case Title(in, 1)  => s"<h1>${inlineToHtml(in)}</h1>"
        case Title(in, 2)  => s"<h2>${inlineToHtml(in)}</h2>"
        case Title(in, 3)  => s"<h3>${inlineToHtml(in)}</h3>"
        case Title(in, _)  => s"<h4>${inlineToHtml(in)}</h4>"
        case Paragraph(in) => s"<p>${inlineToHtml(in)}</p>"
        case Code(data)    => s"""<pre><code class="scala">$data</code></pre>"""
        case UnorderedList(items) =>
          s"<ul>${listItemsToHtml(items)}</ul>"
        case OrderedList(items, listStyle) =>
          s"<ol class=${listStyle}>${listItemsToHtml(items)}</ol>"
        case DefinitionList(items) =>
          s"<dl>${items map { case (t, d) => s"<dt>${inlineToHtml(t)}</dt><dd>${blockToHtml(d)}</dd>" } }</dl>"
        case HorizontalRule() =>
          "<hr/>"
      }

      def listItemsToHtml(items: Seq[Block]) =
        items.foldLeft(""){ (list, item) =>
          item match {
            case OrderedList(_, _) | UnorderedList(_) =>  // html requires sub ULs to be put into the last LI
              list + s"<li>${blockToHtml(item)}</li>"
            case Paragraph(inl) =>
              list + s"<li>${inlineToHtml(inl)}</li>"  // LIs are blocks, no need to use Ps
            case block =>
              list + s"<li>${blockToHtml(block)}</li>"
          }
      }

      bodyToHtml(body)
    }
  }

  case class InlineToHtml(origin: Entity) {
    def apply(inl: Inline) = toHtml(inl)

    def relativePath(target: Entity) =
      util.traversing.relativePath(origin, target)

    def toHtml(inl: Inline): String = inl match {
      case Chain(items)     => (items map toHtml).mkString
      case Italic(in)       => s"<i>${toHtml(in)}</i>"
      case Bold(in)         => s"<b>${toHtml(in)}</b>"
      case Underline(in)    => s"<u>${toHtml(in)}</u>"
      case Superscript(in)  => s"<sup>${toHtml(in)}</sup>"
      case Subscript(in)    => s"<sub>${toHtml(in) }</sub>"
      case Link(raw, title) => s"""<a href=$raw target="_blank">${toHtml(title)}</a>"""
      case Monospace(in)    => s"<code>${toHtml(in)}</code>"
      case Text(text)       => text
      case Summary(in)      => toHtml(in)
      case HtmlTag(tag)     => tag
      case EntityLink(target, link) => enityLinkToHtml(target, link)
    }

    def enityLinkToHtml(target: Inline, link: LinkTo) = link match {
      case Tooltip(_) => toHtml(target)
      case LinkToExternal(n, url) => s"""<a href="$url">$n</a>"""
      case LinkToEntity(t: Entity) => t match {
        // Entity is a package member
        case e: Entity with Members =>
          s"""<a href="${relativePath(t)}">${toHtml(target)}</a>"""
        // Entity is a Val / Def
        case x => x.parent.fold(toHtml(target)) { xpar =>
          s"""<a href="${relativePath(xpar)}#${x.name}">${toHtml(target)}</a>"""
        }
      }
    }
  }
}