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" => "ρ"
295 case "Box" => "⃞"
296 // TO DO: integrate stixfonts stuff
297 case "cent" => "½"
298 case "euro" => "€"
301 val x = mapToHtml(body)
302 if (x.charAt(x.length-1) == '1') x+"<sup>"+"st"+"</sup>"
303 else if (x.charAt(x.length-1) == '2') x+"<sup>"+"nd"+"</sup>"
304 else if (x.charAt(x.length-1) == '3') x+"<sup>"+"rd"+"</sup>"
305 else x+"<sup>"+"th"+"</sup>"
307 // TO DO: use "unicode vulgar fractions" here
308 // directional quotes: see http://www.dwheeler.com/essays/quotes-in-html.html
309 /* u'1/2' : u'\u00BD',
310 // u'1/4' : u'\u00BC',
311 // u'3/4' : u'\u00BE',
312 // u'1/3' : u'\u2153',
313 // u'2/3' : u'\u2154',
314 // u'1/5' : u'\u2155',
315 // u'2/5' : u'\u2156',
316 // u'3/5' : u'\u2157',
317 // u'4/5' : u'\u2158',
318 // u'1/6' : u'\u2159',
319 // u'5/6' : u'\u215A',
320 // u'1/8' : u'\u215B',
321 // u'3/8' : u'\u215C',
322 // u'5/8' : u'\u215D',
323 // u'7/8' : u'\u215E',
325 case "fraction" => "<sup>"++body(0).toHtml++"</sup>"++"/"++"<sub>"++body(1).toHtml++"</sub>"
326 case "rfc" => "<tt><a href=http://tools.ietf.org/html/rfc"+mapToHtml(body)+">RFC"+mapToHtml(body)+"</a></tt>"
327 case "keystroke:command" => "⌘"
328 case "keystroke:shift" => "⇧"
329 case "keystroke:option" => "⌥"
330 case "keystroke:control" => "⌃"
331 case "keystroke:capslock" => "⇪"
332 case "keystroke:apple" => ""
333 case _ => throw new RuntimeException("unsupported command " + command)
338 abstract class Glyph extends ToHtml
339 case object Euro extends Glyph { override def toHtml = "€" }
340 case object CircleR extends Glyph { override def toHtml = "¢" }
341 case object CircleC extends Glyph { override def toHtml = "®" }
342 case object TradeMark extends Glyph { override def toHtml = "™" }
343 case object ServiceMark extends Glyph { override def toHtml = "™" }
344 case object Endash extends Glyph { override def toHtml = "–" }
345 case object Emdash extends Glyph { override def toHtml = "—" }
346 case object Ellipsis extends Glyph { override def toHtml = "…" /* &cdots;? */ }
347 case object Cent extends Glyph { override def toHtml = "½" }
348 case object Daggar extends Glyph { override def toHtml = "†" }
349 case object DoubleDaggar extends Glyph { override def toHtml = "‡" }
350 case object Clover extends Glyph { override def toHtml = "⌘" }
351 case object Flat extends Glyph { override def toHtml = "⋖" }
352 case object Natural extends Glyph { override def toHtml = "⋗" }
353 case object Sharp extends Glyph { override def toHtml = "⋘" }
354 case object CheckMark extends Glyph { override def toHtml = "✓" }
355 case object XMark extends Glyph { override def toHtml = "✗" }
356 case object LeftArrow extends Glyph { override def toHtml = "←" }
357 case object RightArrow extends Glyph { override def toHtml = "→" }
358 case object DoubleLeftArrow extends Glyph { override def toHtml = "&#;" /* FIXME */ }
359 case object DoubleRightArrow extends Glyph { override def toHtml = "&#;" /* FIXME */ }
360 case object DoubleLeftRightArrow extends Glyph { override def toHtml = "&#;" /* FIXME */ }
361 case object LeftRightArrow extends Glyph { override def toHtml = "&#;" /* FIXME */ }
362 case object Degree extends Glyph { override def toHtml = "&#;" /* FIXME */ }
364 class Login(val user:String, val password:Option[String]) {
365 override def toString =
368 case Some(x) => user+":"+urlEscape(x) }
372 case class URLPath(val path:String) extends URL { override def toString = path }
373 case class URLEmail(val user:String, val host:Host) extends URL { override def toString = "mailto:"+user+"@"+host }
374 case class URLNormal(val method:String,
375 val login:Option[Login],
377 val port:Option[Int],
379 val ref:Option[String]) extends URL {
380 override def toString =
384 case Some(log) => log+"@" })+
391 case Some(x) => "#"+x })
397 case class HostIP(val ip0:Int, val ip1:Int, val ip2:Int, val ip3:Int) extends Host {
398 override def toString = ip0+"."+ip1+"."+ip2+"."+ip3 }
399 case class HostDNS(val parts:Seq[String]) extends Host {
400 override def toString = joinStrings(parts, ".") }