1 import edu.berkeley.sbp.scala._
4 import Html.joinStrings
5 import Html.{urlEscape,htmlEscape}
6 import Html.{pre,stag,tag,stag_,tag_,stag0,link}
10 def concatMap[A,B](f: A => Seq[B], s:Seq[A]) : Seq[B] =
13 def concat[A](s:Seq[Seq[A]]) : Seq[A] =
14 s.foldLeft(Seq[A]())(_ ++ _)
16 def docFromTree(t:Tree) : Doc =
17 t match { case Tree(_,Seq(_,Tree(_,a,_)),_) => new Doc(new Header(), a.map(sectionFromTree)) }
19 def sectionFromTree(t:Tree) : Section =
21 case Tree("Section", seq,_) =>
23 case Tree("SectionHeader", Seq(Tree("=",e,_),c),_) =>
24 new Section(e.length-1, textSequenceFromTree(c), paragraphsFromTrees(seq.tail))
28 def textSequenceFromTree (t:Tree) : Seq[Text] =
30 case Tree("Word", chars ,_) => Seq(new Chars(stringFromTrees(chars)))
31 case Tree("Ordinal", x ,_) => Seq(new Command("ordinal", Seq(new Chars(stringFromTrees(x)))))
32 case Tree("Fraction", Seq(n,d) ,_) => Seq(new Command("fraction",Seq(new Chars(stringFromTree(n)),
33 new Chars(stringFromTree(d)))))
34 case Tree("WS", _ ,_) => Seq(WS)
35 case Tree("Quotes", Seq(x) ,_) => Seq(new Quotes(textSequenceFromTree(x)))
36 case Tree("Pars", y ,_) => Seq(new SubPar(paragraphsFromTrees(y)))
37 case Tree("Command", Seq(x,y) ,_) => Seq(new Command(stringFromTree(x), textSequenceFromTree(y)))
38 case Tree("Command", Seq(x) ,_) => Seq(new Command(stringFromTree(x), Seq()))
39 case Tree("Link", Seq(text,link) ,_) => Seq(new Link(urlFromTree(link), textSequenceFromTree(text)))
40 case Tree("Footnote", x ,_) => Seq(new Footnote(concatMap(textSequenceFromTree,x)))
41 case Tree("Keyword", x ,_) => Seq(new Keyword(concatMap(textSequenceFromTree,x)))
42 case Tree("Math", x ,_) => Seq(new Math(stringFromTrees(x)))
43 case Tree("Italic", Seq(x) ,_) => Seq(new Styled(Italic , textSequenceFromTree(x)))
44 case Tree("Bold", Seq(x) ,_) => Seq(new Styled(Bold , textSequenceFromTree(x)))
45 case Tree("Highlight", Seq(x) ,_) => Seq(new Styled(Highlight , textSequenceFromTree(x)))
46 case Tree("TT", x ,_) => Seq(new Styled(TT , concatMap(textSequenceFromTree,x)))
47 case Tree("Strikethrough", x ,_) => Seq(new Styled(Strikethrough , concatMap(textSequenceFromTree,x)))
48 case Tree("Superscript", x ,_) => Seq(new Styled(Superscript , concatMap(textSequenceFromTree,x)))
49 case Tree("Subscript", x ,_) => Seq(new Styled(Subscript , concatMap(textSequenceFromTree,x)))
50 case Tree("Underline", x ,_) => Seq(new Styled(Underline , concatMap(textSequenceFromTree,x)))
51 case Tree("(e)", _ ,_) => Seq(new GlyphText(Euro))
52 case Tree("(r)", _ ,_) => Seq(new GlyphText(CircleR))
53 case Tree("(c)", _ ,_) => Seq(new GlyphText(CircleC))
54 case Tree("(tm)", _ ,_) => Seq(new GlyphText(TradeMark))
55 case Tree("--", _ ,_) => Seq(new GlyphText(Endash))
56 case Tree("---", _ ,_) => Seq(new GlyphText(Emdash))
57 case Tree("<-", _ ,_) => Seq(new GlyphText(LeftArrow))
58 case Tree("<=", _ ,_) => Seq(new GlyphText(DoubleLeftArrow))
59 case Tree("=>", _ ,_) => Seq(new GlyphText(DoubleRightArrow))
60 case Tree("<=>", _ ,_) => Seq(new GlyphText(DoubleLeftRightArrow))
61 case Tree("<->", _ ,_) => Seq(new GlyphText(LeftRightArrow))
62 case Tree("^o", _ ,_) => Seq(new GlyphText(Degree))
63 case Tree("...", _ ,_) => Seq(new GlyphText(Ellipsis))
64 case Tree("Text", ts ,_) => concat(ts.map(textSequenceFromTree))
65 case Tree("", Seq() ,_) => Seq()
66 case t => throw new RuntimeException("unable to create [Text] from " + t)
69 def hostFromTree(t:Tree) : Host =
71 case Tree("IP", Seq(Tree(_,a,_),Tree(_,b,_),Tree(_,c,_),Tree(_,d,_)),_) =>
72 new HostIP(intFromTrees(a), intFromTrees(b), intFromTrees(c), intFromTrees(d))
73 case Tree("DNS", parts,_) =>
74 new HostDNS(parts.map( (t:Tree) => t match { case Tree(_, c,_) => stringFromTrees(c) }))
77 def urlFromTree(t:Tree) : URL =
79 case Tree("URL", stuff,_) => urlFromTrees(stuff)
80 case Tree("Email", Seq(Tree("username", un,_),host),_) => new URLEmail(stringFromTrees(un), hostFromTree(host))
81 case Tree("Path",stuff,_) => new URLPath(stuff.map(fromUrlChar).foldLeft("")(_ + _))
84 def urlFromTrees(t:Seq[Tree]) : URL =
86 case Seq(Tree(_,method,_), login, host, port, rest @_*) =>
87 new URLNormal(stringFromTrees(method),
90 port match { case Tree("Port",port,_) => {
91 val q = stringFromTrees(port)
92 if (q.equals("")) None else Some(java.lang.Integer.parseInt(q))
95 rest match { case Seq(Tree("Path",p,_), x@_*) => p.map(fromUrlChar).foldLeft("")(_ + _)
97 rest match { case Seq(_ , Tree("Path",r,_), x@_*) => Some(stringFromTrees(r))
101 //fromUrlChar (Tree "%" [(Tree a [] _),(Tree b [] _)] _) = chr $ (fst $ head $ readHex (a++b))
102 // FIXME: problem here is the "/" vs "%2F" issue, so we "leave urls urlencoded"
103 def fromUrlChar(t:Tree) : String =
105 case Tree("%", Seq(Tree(a,Seq(),_),Tree(b,Seq(),_)),_) => "%"+a+b
108 else throw new RuntimeException("could not parse as URL char: " + t)
111 def paragraphsFromTrees(ts:Seq[Tree]) : Seq[Paragraph] =
112 consolidate(concatMap(paragraphsFromTree,ts))
114 def paragraphsFromTree(t:Tree) : Seq[Paragraph] =
115 consolidate (t match {
116 case Tree("Verbatim", Seq(indent,v),_) => Seq(new P( Seq(new Verbatim(unindent(indent,unverbate(v))))))
117 case Tree("TextParagraph", Seq(Tree(_,text,_)),_) => Seq(new P(concatMap(textSequenceFromTree,text)))
118 case Tree("Pars", pars ,_) => concatMap(paragraphsFromTree,pars)
119 case Tree("HR", _ ,_) => Seq(HR)
120 case Tree("OL", a ,_) =>
121 Seq(new OL(a.map( (t:Tree) => t match { case Tree("LI",x,_) => paragraphsFromTrees(x)})))
122 case Tree("UL", a ,_) =>
123 Seq(new UL(a.map( (t:Tree) => t match { case Tree("LI",x,_) => paragraphsFromTrees(x)})))
124 case Tree("", _ ,_) => Seq()
125 case Tree("Blockquote", pars ,_) => Seq(Blockquote(paragraphsFromTrees(pars)))
126 case _ => throw new RuntimeException("unable to create [Paragraph] from " + t)
129 def unverbate (t:Tree) : String =
131 case Tree("Verbatim",x,_) => x.map(unverbate).foldLeft("")(_ + _)
132 case Tree("VerbatimBrace",Seq(x,y),_) => unverbate(x)+" "+unverbate(y)
133 case Tree(t,Seq(),_) => t
136 def unindent (t:Tree,v:String) : String =
138 case Tree("I", indent,_) => unindent_(indent.length+1, v)
141 private def unindent_ (i:Int,v:String) : String =
143 else if (v.charAt(0) == '\n') "\n"+unindent_(i, drop_(i, v.substring(1)))
144 else v.charAt(0)+unindent_(i, v.substring(1))
146 private def drop_(n:Int, x:String) : String = {
147 val x_ : Seq[Char] = x;
150 case Seq('\n', r@_*) => x
152 case Seq(a, b@_*) => drop_(n-1, x.substring(1))
156 def consolidate(x:Seq[Paragraph]) : Seq[Paragraph] =
159 case Seq(a) => Seq(a)
160 case Seq(OL(Seq()), x@_*) => consolidate(x)
161 case Seq(UL(Seq()), x@_*) => consolidate(x)
162 case Seq(OL(a), OL(b), x@_*) => consolidate(Seq(OL(a++b))++x)
163 case Seq(UL(a), UL(b), x@_*) => consolidate(Seq(UL(a++b))++x)
164 case Seq(a, b @_*) => Seq(a)++consolidate(b)
167 def intFromTrees(t:Seq[Tree]) : Int =
168 java.lang.Integer.parseInt(stringFromTrees(t))
169 def stringFromTree(t:Tree) : String =
170 t match { case Tree(h,c,_) => h++concatMap(stringFromTree,c) }
171 def stringFromTrees(ts:Seq[Tree]) : String =
172 ts.map(stringFromTree).foldLeft("")(_ + _)
176 class Doc (val header:Header, val sections:Seq[Section]) extends ToHtml {
177 override def toHtml =
178 "<!-- This document was AUTOMATICALLY GENERATED from wix source -->\n"+
179 "<!-- it is probably not a wise idea to edit it directly -->\n\n"+
185 "<body>\n"+ // tell jsmath we will escape stuff manually
186 Html.jsMath+ // FIXME: only put this in if math appears on the page
187 "<center><table><tr><td width=600>\n"+
190 "<table width=100% class=footer><tr><td align=left>"+
191 "<img src='"+Html.printIconBase64+"'></td>"+
192 "<td align=right><span class='signature'>rendered from "+
193 "<a href=http://www.megacz.com/software/wix>"+
194 "W<span style='vertical-align:-20%'>I</span>X</a></span></div></td></tr></table>\n"+
195 "</td></tr></table></center>\n"+
201 class Section (val level:Int, val header:Seq[Text], val paragraphs:Seq[Paragraph]) extends ToHtml {
202 def toHtml = "\n<h"+((level+1))+">\n"+(mapToHtml(header))+"\n</h"+((level+1))+">\n"+(mapToHtml(paragraphs)) }
204 abstract class Paragraph extends ToHtml
205 case class P (val body :Seq[Text] ) extends Paragraph { override def toHtml = stag_("p",body) }
206 case object HR extends Paragraph { override def toHtml = stag("hr") }
207 case class OL (val items:Seq[Seq[Paragraph]] ) extends Paragraph {
208 override def toHtml = stag0("ol", items.map( (s:Seq[Paragraph]) => stag_("li", s) ).foldLeft("")(_ + _)) }
209 case class UL (val items:Seq[Seq[Paragraph]] ) extends Paragraph {
210 override def toHtml = stag0("ul", items.map( (s:Seq[Paragraph]) => stag_("li", s) ).foldLeft("")(_ + _)) }
211 case class Blockquote (val body :Seq[Paragraph] ) extends Paragraph {
212 override def toHtml =
213 "\n<table class=blockquote border=0 cellpadding=5px>\n"+
214 "<tr><td valign=top><image src='"+Html.quoteIconBase64+"'></td>\n"+
217 "</td></tr></table>\n"
221 case object TT extends Style
222 case object Underline extends Style
223 case object Superscript extends Style
224 case object Subscript extends Style
225 case object Strikethrough extends Style
226 case object Italic extends Style
227 case object Bold extends Style
228 case object Highlight extends Style
230 abstract class Text extends ToHtml
231 case object WS extends Text { def toHtml = " " }
232 case class Chars(val body:String) extends Text { def toHtml = htmlEscape(body) }
233 case class Quotes(val body:Seq[Text]) extends Text { def toHtml = "“"+mapToHtml(body)+"”" }
234 case class GlyphText(val body:Glyph) extends Text { override def toHtml = body.toHtml }
235 case class Math(val body:String) extends Text { override def toHtml = "<span class=math>" +body+ "</span>" }
236 case class Verbatim(val body:String) extends Text { override def toHtml = pre(body) }
237 case class Link(val url:URL, val body:Seq[Text]) extends Text { override def toHtml = link(url.toString, body) }
238 case class Footnote(val body:Seq[Text]) extends Text { override def toHtml = throw new Exception() }
239 case class Keyword(val body:Seq[Text]) extends Text { override def toHtml = tag_("tt", body) }
240 case class SubPar(val body:Seq[Paragraph]) extends Text { override def toHtml = stag_("p", body) }
241 case class Styled(val style:Style, val body:Seq[Text]) extends Text {
242 override def toHtml =
244 case Underline => tag_("u", body)
245 case TT => tag_("tt", body)
246 case Italic => tag_("i", body)
247 case Strikethrough => tag_("strike", body)
248 case Superscript => tag_("sup", body)
249 case Subscript => tag_("sub", body)
250 case Bold => tag_("b", body)
251 case Highlight => "<span class=highlight>"+mapToHtml(body)+"</span>"
254 case class Command(val command:String, val body:Seq[Text]) extends Text {
255 override def toHtml =
258 case "url" => "<tt>"+link(mapToHtml(body),body)+"</tt>"
259 case "WiX" => "W<span style='vertical-align:-20%'>I</span>X"
260 case "TeX" => "T<span style='vertical-align:-20%'>E</span>X"
261 case "red" => "<font color=red>"+mapToHtml(body)+"</font>"
262 case "orange" => "<font color=orange>"+mapToHtml(body)+"</font>"
263 case "green" => "<font color=green>"+mapToHtml(body)+"</font>"
264 case "sc" => "<sc>"+mapToHtml(body)+"</sc>"
265 case "image" => "<img src='"+mapToHtml(body)+"'/>"
266 case "imagec" => "<center><img src='"+mapToHtml(body)+"'/></center>"
267 case "image2" => "<img width=180px src='"+mapToHtml(body)+"'/>"
268 case "image3" => "<img width=200px src='"+mapToHtml(body)+"'/>"
269 case "image4" => "<center><img width=550px src='"+mapToHtml(body)+"'/></center>"
270 case "warn" => "\n<div class=warn>\n<table border=0 cellpadding=5px>\n"+
271 "<tr><td valign=top><image src='"+Html.warnIconBase64+"'></td>\n"+
274 "</td></tr></table></div>\n"
275 case "announce" => "\n<div class=announce>\n<table border=0 cellpadding=5px>\n"+
276 "<tr><td valign=top></td>\n"+
279 "</td></tr></table></div>\n"
280 case "br" => "\n<br/>\n"
281 case "alpha" => "α"
282 case "beta" => "β"
283 case "gamma" => "γ"
284 case "delta" => "δ"
285 case "epsilon" => "ε"
286 case "eta" => "η"
287 case "theta" => "θ"
288 case "kappa" => "κ"
289 case "lambda" => "λ"
290 case "mu" => "μ"
291 case "nu" => "ν"
292 case "pi" => "π"
293 case "rho" => "ρ"
294 // TO DO: integrate stixfonts stuff
295 case "cent" => "½"
296 case "euro" => "€"
299 val x = mapToHtml(body)
300 if (x.charAt(x.length-1) == '1') x+"<sup>"+"st"+"</sup>"
301 else if (x.charAt(x.length-1) == '2') x+"<sup>"+"nd"+"</sup>"
302 else if (x.charAt(x.length-1) == '3') x+"<sup>"+"rd"+"</sup>"
303 else x+"<sup>"+"th"+"</sup>"
305 // TO DO: use "unicode vulgar fractions" here
306 // directional quotes: see http://www.dwheeler.com/essays/quotes-in-html.html
307 /* u'1/2' : u'\u00BD',
308 // u'1/4' : u'\u00BC',
309 // u'3/4' : u'\u00BE',
310 // u'1/3' : u'\u2153',
311 // u'2/3' : u'\u2154',
312 // u'1/5' : u'\u2155',
313 // u'2/5' : u'\u2156',
314 // u'3/5' : u'\u2157',
315 // u'4/5' : u'\u2158',
316 // u'1/6' : u'\u2159',
317 // u'5/6' : u'\u215A',
318 // u'1/8' : u'\u215B',
319 // u'3/8' : u'\u215C',
320 // u'5/8' : u'\u215D',
321 // u'7/8' : u'\u215E',
323 case "fraction" => "<sup>"++body(0).toHtml++"</sup>"++"/"++"<sub>"++body(1).toHtml++"</sub>"
324 case "rfc" => "<tt><a href=http://tools.ietf.org/html/rfc"+mapToHtml(body)+">RFC"+mapToHtml(body)+"</a></tt>"
325 case "keystroke:command" => "⌘"
326 case "keystroke:shift" => "⇧"
327 case "keystroke:option" => "⌥"
328 case "keystroke:control" => "⌃"
329 case "keystroke:capslock" => "⇪"
330 case "keystroke:apple" => ""
331 case _ => throw new RuntimeException("unsupported command " + command)
336 abstract class Glyph extends ToHtml
337 case object Euro extends Glyph { override def toHtml = "€" }
338 case object CircleR extends Glyph { override def toHtml = "¢" }
339 case object CircleC extends Glyph { override def toHtml = "®" }
340 case object TradeMark extends Glyph { override def toHtml = "™" }
341 case object ServiceMark extends Glyph { override def toHtml = "™" }
342 case object Endash extends Glyph { override def toHtml = "–" }
343 case object Emdash extends Glyph { override def toHtml = "—" }
344 case object Ellipsis extends Glyph { override def toHtml = "…" /* &cdots;? */ }
345 case object Cent extends Glyph { override def toHtml = "½" }
346 case object Daggar extends Glyph { override def toHtml = "†" }
347 case object DoubleDaggar extends Glyph { override def toHtml = "‡" }
348 case object Clover extends Glyph { override def toHtml = "⌘" }
349 case object Flat extends Glyph { override def toHtml = "⋖" }
350 case object Natural extends Glyph { override def toHtml = "⋗" }
351 case object Sharp extends Glyph { override def toHtml = "⋘" }
352 case object CheckMark extends Glyph { override def toHtml = "✓" }
353 case object XMark extends Glyph { override def toHtml = "✗" }
354 case object LeftArrow extends Glyph { override def toHtml = "←" }
355 case object RightArrow extends Glyph { override def toHtml = "→" }
356 case object DoubleLeftArrow extends Glyph { override def toHtml = "&#;" /* FIXME */ }
357 case object DoubleRightArrow extends Glyph { override def toHtml = "&#;" /* FIXME */ }
358 case object DoubleLeftRightArrow extends Glyph { override def toHtml = "&#;" /* FIXME */ }
359 case object LeftRightArrow extends Glyph { override def toHtml = "&#;" /* FIXME */ }
360 case object Degree extends Glyph { override def toHtml = "&#;" /* FIXME */ }
362 class Login(val user:String, val password:Option[String]) {
363 override def toString =
366 case Some(x) => user+":"+urlEscape(x) }
370 case class URLPath(val path:String) extends URL { override def toString = path }
371 case class URLEmail(val user:String, val host:Host) extends URL { override def toString = "mailto:"+user+"@"+host }
372 case class URLNormal(val method:String,
373 val login:Option[Login],
375 val port:Option[Int],
377 val ref:Option[String]) extends URL {
378 override def toString =
382 case Some(log) => log+"@" })+
389 case Some(x) => "#"+x })
395 case class HostIP(val ip0:Int, val ip1:Int, val ip2:Int, val ip3:Int) extends Host {
396 override def toString = ip0+"."+ip1+"."+ip2+"."+ip3 }
397 case class HostDNS(val parts:Seq[String]) extends Host {
398 override def toString = joinStrings(parts, ".") }