rewrite Haskell parts in Scala
[wix.git] / src / Html.scala
diff --git a/src/Html.scala b/src/Html.scala
new file mode 100644 (file)
index 0000000..8ffdea3
--- /dev/null
@@ -0,0 +1,216 @@
+trait ToHtml {
+  def toHtml : String
+}
+
+object Html {
+
+  def style : String =
+    "\n<style>\n"+
+    " h1, h2, h3, h4 { font-family: 'Trebuchet MS', arial, verdana, sans-serif; width: 100% }\n"+
+    " h1 { font-size: 20pt; border-top: black 1px solid; }\n"+
+    " h2 { font-size: 16pt; border-top: silver 1px solid; }\n"+
+    " h3 { font-size: 12pt; }\n"+
+    " TH, TD, P, LI, DIV, SPAN {\n"+
+    "     font-family: verdana, arial, sans-serif;\n"+
+    "     font-size: 12px;  \n"+
+    "     text-decoration:none; \n"+
+    " }\n"+
+    " LI { margin-top: 5px; }\n"+
+    " body { color: #333333; }\n"+
+    " blockquote { font-style: italic; width: 100% }\n"+
+    " div.warn { border: 1px solid #ff; border-top: 5px solid #ff; background-color: #fbb; color: white; }\n"+
+    " td.warn { color: black; }\n"+
+    " div.announce { border: 1px solid green; background-color: #bfb; color: white; }\n"+
+    " td.announce { color: black; }\n"+
+    " div.footer {\n"+
+    "   color: gray;\n"+
+    "   border-top: 1px solid silver;\n"+
+    "   font-size: 10px;\n"+
+    " }\n"+
+    " table.blockquote { margin: 5px; border: 1px #e6ddcb solid; background-color: #fbf2e0; width: 100% }\n"+
+    " a:link { text-decoration: none; color: blue; border-bottom:1px dotted; }\n"+
+    " a:visited { text-decoration: none; color: purple; border-bottom:1px dotted; }\n"+
+    " a:active { text-decoration: none; color: red; border-bottom:1px solid; }\n"+
+    " a:hover { text-decoration: none; border-bottom:1px solid; }\n"+
+    " table.footer { border-top: silver solid 1px; }\n"+
+    " span.signature { color: #bbb;  }\n"+
+    " .signature a:link { color: #aaf;  }\n"+
+    " .signature a:visited { color: #faa;  }\n"+
+    " .signature a:hover { color: blue; border-bottom: 1px solid blue;  }\n"+
+    " span.highlight { background: yellow; color: black; padding: 3px }\n"+
+    " div.pre {\n"+
+    "     text-align: left;\n"+
+    "     font-family: monospace;\n"+
+    "     border-style: none;\n"+
+    "     border-width: 2px 2px 2px 2px;\n"+
+    "     border-color: #6666aa;\n"+
+    "     color: #FFFFFF;\n"+
+    "     background-color: #333333;\n"+
+    "     margin-right: 25px;\n"+
+    "     margin-left: 25px;\n"+
+    "     padding: 10px;\n"+
+    "  }\n"+
+    "</style>\n"
+
+  def quoteIconBase64 = 
+    "data:image/png;base64,"+
+    "iVBORw0KGgoAAAANSUhEUgAAABAAAAAVCAYAAABPPm7SAAAABmJLR0QA/wD/AP+gvaeTAAAA"+
+    "CXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH1wQPAx0rP5obpAAAAWJJREFUOMvdlKFvg0AU"+
+    "xr8tVZ1cbU+04lUuwc2WypMX9AxiomoJ/RNYqmaaYKpb5kBys1UkWBCdKLqVs8zwFnrQmYol"+
+    "+xLC8X55H/e+wAENhWGoPM/zT6eThQ4lSWJf5EEQuAAqAJXv+95vXCkVtpwZuq4bmM1pmlrM"+
+    "LctKWzuwbTsBUNX3lph3Njfd6/WZ9vv9iHkYhsrkPa21zQ9aa9v3fRsAFovFK9cMPgIApdT7"+
+    "eDz+RB1Y1XEBwEVe54ZbXKm/N7haNwCQ5zmtVqtnE0op49lspgFgPp+/dfF/EGIPAAaDgZBS"+
+    "xgBwPB7vd7vdIwD0+/2v5ry8juNYmgY/P1GWZQ9sQER3XOcwD4fDkA2IqGiNwN8+ERVNY/Pt"+
+    "RFQIIcozg+Vy+VKW5dDcMmuz2ThFURAATKfTj7MQ1+v1Ezc7jrMVQpTmocOjOY6znUwmRSvR"+
+    "KIpknud0KfEoimSWZQ/N2jfhtb1AvGklDQAAAABJRU5ErkJggg=="
+
+  def warnIconBase64 = 
+    "data:image/png;base64,"+
+    "iVBORw0KGgoAAAANSUhEUgAAABQAAAAREAYAAACN1FD9AAAABmJLR0QA/wD/AP+gvaeTAAAA"+
+    "CXBIWXMAAABIAAAASABGyWs+AAAACXZwQWcAAAAUAAAAEQDeTN6UAAAD/ElEQVRIx8WUbUyV"+
+    "ZRjHf+chxDi8yZcDa0mKh/wSuNE5eNYHZBiRwoespayEkdgx22hmjB1glTlenDYlMccAX1cb"+
+    "CDFlg+aQ1JGEazGWpixdbbweDucctPF2PNxXX3pw00gL09+X59m1+77+v+d+nueCBRJ+ECAs"+
+    "78dnAPp2nLEDtMdpPwMYZhfaf8GU1gLsixwdBRDRr7lRAHmxT0zs+UKAuKmhXgDfGleAYZVh"+
+    "lXynC/Z1AYyeX+IFiLjw+MwWAxDQuBqgNVsX+iNta/nWcnV0vD+9Ir1CndDre68AfDH42PzW"+
+    "2QDW79UFxkxh6WHp8o5a5lznXCfJ/nPX867nSYArO7AssEyOjOwE8GfGZwC84Pu3edrDLgzK"+
+    "AFj06q7DAPt/1evBK4szizMlpXy4tqO2g/MHa9sm2ibwP23M78zvFE0rBAg4U24DqFpieAqA"+
+    "6kd+cjuiAAqu6Sfn/n55/fJ6CZOc6YHpATmkNWgNWoOIZtSMmlFE1d9ae2utvDt21ZRiSpHV"+
+    "+r43LwFkBT4ysehigKgDvxcC3P5QD5qxNtU11anj8hfhg+GD4YMixipjlbFK5pjyHok8Enn3"+
+    "m7yyG2DAEdIFYLy9YMHDLoBjK/WA8fhkb7JXtcqMnJbT4tJFYlpiWmJaRKJio2KjYu8KSp9K"+
+    "UAly2ZtpSbWkql/0PrsuAJR3/2cx65cA1qujRgBV6HpWy9fy5dydOz2JPYlqQO4hwZ/gT/CL"+
+    "mB1mh9kh93Enq2tP1x7V4wo0pBnS5KehVwBmPjA7AcxtDy2mBQIYPmk/B/CDb26MxGw5teWU"+
+    "+krmIdWaak21iiR2JHYkdsi83G7KtmRb1HG9b+PHAK2lJAEQ9EDBt88C5BybGyOLQgtCC8Su"+
+    "gkesI1axzRd8Mvdk7slckeoN1RuqN8wvODsyVDNUIxaXP0SFKHlfz8l4GSDTfd+B6Tdh5QCh"+
+    "hUUvAlR8o9eDI4qSipLkJcOEqdvUzaX5HmzSNGmaNMFN+037TTvgxInzb96QKTovOo/LRmuJ"+
+    "r8QnFr3+2esAB/IX9wAEjd+3cfc+gD2euTFSv2zzss0SIdnTG6c3SqU8AFulrdJWKRJSElIS"+
+    "UiLia/Y1+5r/YcNHM9tntku1e2KFZ4VHAvXcwnyAksY5MfNvAOYdQ+8BzHytL5warDtRd2I2"+
+    "QjWNh46HqjR11LvGu0aVqUOei56LYlD7PRWeCmlWpe4p95R87h66Yb9hV+7+o71BvUHKoWyj"+
+    "jlGHSlbPOTc5N6kkFTqybWSbeksxfHb4rGqd9Q3nDOdI5lRHTXtNu6rWc/vfAJjoXHoNYOl6"+
+    "Q70CaFEpYwAZhof+m/5nvv0UoDFLW/UagCXuSQvdSzwAiTv/BGXg1AxNKyCeAAAAAElFTkSu"+
+    "QmCC"
+
+  def printIconBase64 = 
+    "data:image/png;base64,"+
+    "iVBORw0KGgoAAAANSUhEUgAAAA8AAAAOCAMAAADHVLbdAAAALHRFWHRDcmVhdGlvbiBUaW1l"+
+    "AEZyaSAxOSBTZXAgMjAwMyAxODozOTozMiAtMDAwME2jAt8AAAAHdElNRQfTCRMRKABXeznM"+
+    "AAAACXBIWXMAAAsSAAALEgHS3X78AAAABGdBTUEAALGPC/xhBQAAACRQTFRF////AAAA7+/v"+
+    "3t7ezs7OtbW1ra2tlJSUnJycEBAQKSkphISEbGtEogAAAAF0Uk5TAEDm2GYAAABLSURBVHja"+
+    "nY1BEsAgCAMDKm31//81qB3l6h4Y4mYQQNvAmXNv7W/gytcTRj6pppQHNWoW6JWS13Ix86yr"+
+    "O7MEPkDWuWLPK/7hoYEOxksDsk8eppEAAAAASUVORK5CYII="
+
+  def emailIconBase64 =
+    "data:image/png;base64,"+
+    "iVBORw0KGgoAAAANSUhEUgAAABUAAAAOCAMAAAD32Kf8AAAALHRFWHRDcmVhdGlvbiBUaW1l"+
+    "AEZyaSAxOSBTZXAgMjAwMyAxODo0MjowOSAtMDAwMBDwv7IAAAAHdElNRQfTCRMRKhqYL6I0"+
+    "AAAACXBIWXMAAAsSAAALEgHS3X78AAAABGdBTUEAALGPC/xhBQAAADBQTFRF////AAAAhISE"+
+    "9/fv9/f3////CAgI7+/v1tbOxsa9tbWtpaWclJSMlJSUe3t7a2trxDv8WgAAAAF0Uk5TAEDm"+
+    "2GYAAABiSURBVHjabc9JEoAgDETRpNOKs/e/rSEYF8jfhHpQFAi6JHpHBg5VOdIj+KeXkgO9"+
+    "tUj/Bldo3WdRnktt3fbjgoWKK5EKsunkyryCn86Oxsj/UZqKEkFmNF+mvieFdSK16wFr7QK5"+
+    "tASqkwAAAABJRU5ErkJggg=="
+
+  def pdfIconBase64 = 
+    "data:image/png;base64,"+
+    "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAK3RFWHRDcmVhdGlvbiBUaW1l"+
+    "AFRodSA2IE5vdiAyMDAzIDE1OjMwOjAwIC0wMDAwSwt8PwAAAAd0SU1FB9MLBg8fD1x8/t4A"+
+    "AAAJcEhZcwAACxIAAAsSAdLdfvwAAAAEZ0FNQQAAsY8L/GEFAAAAQlBMVEX///9zc3Nra2uE"+
+    "hITGxsaUlJScnJx7e3uMjIz////39/fv7+/n5+fe3t69vb1jY2OlpaXW1ta1tbVaWlqtra3O"+
+    "zs5w48BYAAAAAXRSTlMAQObYZgAAAIdJREFUeNpNz1ESBBEMBNCRwSCJMOT+V10WW9vKz6uO"+
+    "iusa0R137ShxjImwHsnSMCbEX6dPiIje+SUKC6hav8CbAQlZD/RmJ1Tdz6oTwETMfAByK0hT"+
+    "1kgH7E1phFfjdvS2JlJpN8RAyEHMhgJgaexJTBPyA1JiGoD0BXgg2H8wcFs3/jDvPB+sOwir"+
+    "+o6iKQAAAABJRU5ErkJggg=="
+
+  def jsMath =
+    "<script> jsMath = { showFontWarnings: false } </script>\n"+
+    "<script src='/jsmath/easy/load.js'></script>\n"+
+    "<span id='tex2math_off'></span>\n"+
+    "<NOSCRIPT> <DIV STYLE='color:#CC0000; text-align:center'> "+
+    "<B>Warning: <A HREF='http://www.math.union.edu/locate/jsMath'>jsMath</A> "+
+    "requires JavaScript to process the mathematics on this page.<BR> If your "+
+    "browser supports JavaScript, be sure it is enabled.</B> </DIV> <HR> </NOSCRIPT>\n"
+
+  def urlEscape (s:Seq[Char]) =
+    s.map(urlEscapeChar).foldLeft("")(_ + _)
+
+  def htmlEscape (s:Seq[Char]) =
+    s.map(htmlEscapeChar).foldLeft("")(_ + _)
+
+  private def urlEscapeChar (c:Char) : String =
+    c match {
+      // non-alphanumerics which may appear unescaped
+      case '$'                        => "$"
+      case '-'                        => "-"
+      case '_'                        => "_"
+      case '.'                        => "."
+      case '!'                        => "!"
+      case '*'                        => "*"
+      case '\''                       => "\'"
+      case '('                        => "("
+      case ')'                        => ")"
+      case ','                        => ","
+
+      // technically these aren't allowed by RFC, but we include them anyways
+      case '/'                        => "/"
+      case ';'                        => ";"
+      case '&'                        => "&"
+      case '='                        => "="
+
+      // FIXME: this will wind up "disencoding" a %-encoded question mark
+      case '?'                        => "?"
+
+      case _ => {
+        if ((c >= 'a' && c <= 'z') ||
+            (c >= 'A' && c <= 'Z') ||
+            (c >= '0' && c <= '9'))
+          'c'+""
+        else {
+          val s = "00"+java.lang.Integer.toString(c & 0xff, 16)
+          return '%'+s.substring(s.length()-1, 2)
+        }
+      }
+    }
+
+  private def htmlEscapeChar (c:Char) =
+    c match {
+      case '<'  => "&lt;"
+      case '>'  => "&gt;"
+      case '&'  => "&amp;"
+      case '\'' => "&apos;"
+      case '\"' => "&quot;"
+      case c    => ""+c
+    }
+
+  def mapToHtml[A <: ToHtml](h:Seq[A]) : String =
+    h.map((s:A) => s.toHtml).foldLeft("")(_ + _)
+
+  def joinStrings(strings:Seq[String], separator:String) =
+    if (strings.length == 0) ""
+    else if (strings.length == 1) strings(0)
+    else strings(0)+strings.tail.map( (s:String) => separator+s ).foldLeft("")(_ + _)
+
+  def stag(t:String)                            = "\n<"+t+"></"+t+">\n"
+  def stag(t:String,  body:ToHtml)              = "\n<"+t+">\n"+body.toHtml+"\n</"+t+">\n"
+  def stag0(t:String, body:String)              = "\n<"+t+">\n"+body+"\n</"+t+">\n"
+  def tag(t:String,   body:ToHtml)              = "<"+t+">"+body.toHtml+"</"+t+">"
+  def stag_(t:String, body:Seq[ToHtml])  = "\n<"+t+">\n"+mapToHtml(body)+"\n</"+t+">\n"
+  def tag_(t:String,  body:Seq[ToHtml])  = "<"+t+">"+mapToHtml(body)+"</"+t+">"
+
+  def link(ref:String, body:Seq[ToHtml]) : String = {
+    val img = "style='vertical-align: text-bottom;' border=0 "
+    val icon = if      (ref.endsWith(".pdf"))      "<img "+img+" src='"+Html.pdfIconBase64+"'>&nbsp;"
+               else if (ref.startsWith("mailto:")) "<img "+img+" src='"+Html.emailIconBase64+"'>&nbsp;"
+               else ""
+    return "<a href='"+ref+"'>"+icon+mapToHtml(body)+"</a>"
+  }
+
+  private def pre_(c:Char) : String =
+    c match {
+      case ' '  => "&nbsp;"
+      case '\n' => "<br/>\n"
+      case a    => htmlEscapeChar(a)
+    }
+
+  def pre(x:String) : String =
+    "\n<div class=pre style='white-space:nowrap'>"+(x.map(pre_).foldLeft("")((a,b:String) => a + b))+"\n</div>\n"
+
+}
+