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(Emdash))
56 case Tree("<-", _ ,_) => Seq(new GlyphText(LeftArrow))
57 case Tree("<=", _ ,_) => Seq(new GlyphText(DoubleLeftArrow))
58 case Tree("=>", _ ,_) => Seq(new GlyphText(DoubleRightArrow))
59 case Tree("<=>", _ ,_) => Seq(new GlyphText(DoubleLeftRightArrow))
60 case Tree("<->", _ ,_) => Seq(new GlyphText(LeftRightArrow))
61 case Tree("^o", _ ,_) => Seq(new GlyphText(Degree))
62 case Tree("...", _ ,_) => Seq(new GlyphText(Ellipsis))
63 case Tree("Text", ts ,_) => concat(ts.map(textSequenceFromTree))
64 case Tree("", Seq() ,_) => Seq()
65 case t => throw new RuntimeException("unable to create [Text] from " + t)
68 def hostFromTree(t:Tree) : Host =
70 case Tree("IP", Seq(Tree(_,a,_),Tree(_,b,_),Tree(_,c,_),Tree(_,d,_)),_) =>
71 new HostIP(intFromTrees(a), intFromTrees(b), intFromTrees(c), intFromTrees(d))
72 case Tree("DNS", parts,_) =>
73 new HostDNS(parts.map( (t:Tree) => t match { case Tree(_, c,_) => stringFromTrees(c) }))
76 def urlFromTree(t:Tree) : URL =
78 case Tree("URL", stuff,_) => urlFromTrees(stuff)
79 case Tree("Email", Seq(Tree("username", un,_),host),_) => new URLEmail(stringFromTrees(un), hostFromTree(host))
80 case Tree("Path",stuff,_) => new URLPath(stuff.map(fromUrlChar).foldLeft("")(_ + _))
83 def urlFromTrees(t:Seq[Tree]) : URL =
85 case Seq(Tree(_,method,_), login, host, port, rest @_*) =>
86 new URLNormal(stringFromTrees(method),
89 port match { case Tree("Port",port,_) => {
90 val q = stringFromTrees(port)
91 if (q.equals("")) None else Some(java.lang.Integer.parseInt(q))
94 rest match { case Seq(Tree("Path",p,_), x@_*) => p.map(fromUrlChar).foldLeft("")(_ + _)
96 rest match { case Seq(_ , Tree("Path",r,_), x@_*) => Some(stringFromTrees(r))
100 //fromUrlChar (Tree "%" [(Tree a [] _),(Tree b [] _)] _) = chr $ (fst $ head $ readHex (a++b))
101 // FIXME: problem here is the "/" vs "%2F" issue, so we "leave urls urlencoded"
102 def fromUrlChar(t:Tree) : String =
104 case Tree("%", Seq(Tree(a,Seq(),_),Tree(b,Seq(),_)),_) => "%"+a+b
107 else throw new RuntimeException("could not parse as URL char: " + t)
110 def paragraphsFromTrees(ts:Seq[Tree]) : Seq[Paragraph] =
111 consolidate(concatMap(paragraphsFromTree,ts))
113 def paragraphsFromTree(t:Tree) : Seq[Paragraph] =
114 consolidate (t match {
115 case Tree("Verbatim", Seq(indent,v),_) => Seq(new P( Seq(new Verbatim(unindent(indent,unverbate(v))))))
116 case Tree("TextParagraph", Seq(Tree(_,text,_)),_) => Seq(new P(concatMap(textSequenceFromTree,text)))
117 case Tree("Pars", pars ,_) => concatMap(paragraphsFromTree,pars)
118 case Tree("HR", _ ,_) => Seq(HR)
119 case Tree("OL", a ,_) =>
120 Seq(new OL(a.map( (t:Tree) => t match { case Tree("LI",x,_) => paragraphsFromTrees(x)})))
121 case Tree("UL", a ,_) =>
122 Seq(new UL(a.map( (t:Tree) => t match { case Tree("LI",x,_) => paragraphsFromTrees(x)})))
123 case Tree("", _ ,_) => Seq()
124 case Tree("Blockquote", pars ,_) => Seq(Blockquote(paragraphsFromTrees(pars)))
125 case _ => throw new RuntimeException("unable to create [Paragraph] from " + t)
128 def unverbate (t:Tree) : String =
130 case Tree("Verbatim",x,_) => x.map(unverbate).foldLeft("")(_ + _)
131 case Tree("VerbatimBrace",Seq(x,y),_) => unverbate(x)+" "+unverbate(y)
132 case Tree(t,Seq(),_) => t
135 def unindent (t:Tree,v:String) : String =
137 case Tree("I", indent,_) => unindent_(indent.length+1, v)
140 private def unindent_ (i:Int,v:String) : String =
142 else if (v.charAt(0) == '\n') "\n"+unindent_(i, drop_(i, v.substring(1)))
143 else v.charAt(0)+unindent_(i, v.substring(1))
145 private def drop_(n:Int, x:String) : String = {
146 val x_ : Seq[Char] = x;
149 case Seq('\n', r@_*) => x
151 case Seq(a, b@_*) => drop_(n-1, x.substring(1))
155 def consolidate(x:Seq[Paragraph]) : Seq[Paragraph] =
158 case Seq(a) => Seq(a)
159 case Seq(OL(Seq()), x@_*) => consolidate(x)
160 case Seq(UL(Seq()), x@_*) => consolidate(x)
161 case Seq(OL(a), OL(b), x@_*) => consolidate(Seq(OL(a++b))++x)
162 case Seq(UL(a), UL(b), x@_*) => consolidate(Seq(UL(a++b))++x)
163 case Seq(a, b @_*) => Seq(a)++consolidate(b)
166 def intFromTrees(t:Seq[Tree]) : Int =
167 java.lang.Integer.parseInt(stringFromTrees(t))
168 def stringFromTree(t:Tree) : String =
169 t match { case Tree(h,c,_) => h++concatMap(stringFromTree,c) }
170 def stringFromTrees(ts:Seq[Tree]) : String =
171 ts.map(stringFromTree).foldLeft("")(_ + _)
175 class Doc (val header:Header, val sections:Seq[Section]) extends ToHtml {
176 override def toHtml =
177 "<!-- This document was AUTOMATICALLY GENERATED from wix source -->\n"+
178 "<!-- it is probably not a wise idea to edit it directly -->\n\n"+
184 "<body>\n"+ // tell jsmath we will escape stuff manually
185 Html.jsMath+ // FIXME: only put this in if math appears on the page
186 "<center><table><tr><td width=600>\n"+
189 "<table width=100% class=footer><tr><td align=left>"+
190 "<img src='"+Html.printIconBase64+"'></td>"+
191 "<td align=right><span class='signature'>rendered from "+
192 "<a href=http://www.megacz.com/software/wix>"+
193 "W<span style='vertical-align:-20%'>I</span>X</a></span></div></td></tr></table>\n"+
194 "</td></tr></table></center>\n"+
200 class Section (val level:Int, val header:Seq[Text], val paragraphs:Seq[Paragraph]) extends ToHtml {
201 def toHtml = "\n<h"+((level+1))+">\n"+(mapToHtml(header))+"\n</h"+((level+1))+">\n"+(mapToHtml(paragraphs)) }
203 abstract class Paragraph extends ToHtml
204 case class P (val body :Seq[Text] ) extends Paragraph { override def toHtml = stag_("p",body) }
205 case object HR extends Paragraph { override def toHtml = stag("hr") }
206 case class OL (val items:Seq[Seq[Paragraph]] ) extends Paragraph {
207 override def toHtml = stag0("ol", items.map( (s:Seq[Paragraph]) => stag_("li", s) ).foldLeft("")(_ + _)) }
208 case class UL (val items:Seq[Seq[Paragraph]] ) extends Paragraph {
209 override def toHtml = stag0("ul", items.map( (s:Seq[Paragraph]) => stag_("li", s) ).foldLeft("")(_ + _)) }
210 case class Blockquote (val body :Seq[Paragraph] ) extends Paragraph {
211 override def toHtml =
212 "\n<table class=blockquote border=0 cellpadding=5px>\n"+
213 "<tr><td valign=top><image src='"+Html.quoteIconBase64+"'></td>\n"+
216 "</td></tr></table>\n"
220 case object TT extends Style
221 case object Underline extends Style
222 case object Superscript extends Style
223 case object Subscript extends Style
224 case object Strikethrough extends Style
225 case object Italic extends Style
226 case object Bold extends Style
227 case object Highlight extends Style
229 abstract class Text extends ToHtml
230 case object WS extends Text { def toHtml = " " }
231 case class Chars(val body:String) extends Text { def toHtml = htmlEscape(body) }
232 case class Quotes(val body:Seq[Text]) extends Text { def toHtml = "“"+mapToHtml(body)+"”" }
233 case class GlyphText(val body:Glyph) extends Text { override def toHtml = body.toHtml }
234 case class Math(val body:String) extends Text { override def toHtml = "<span class=math>" +body+ "</span>" }
235 case class Verbatim(val body:String) extends Text { override def toHtml = pre(body) }
236 case class Link(val url:URL, val body:Seq[Text]) extends Text { override def toHtml = link(url.toString, body) }
237 case class Footnote(val body:Seq[Text]) extends Text { override def toHtml = throw new Exception() }
238 case class Keyword(val body:Seq[Text]) extends Text { override def toHtml = tag_("tt", body) }
239 case class SubPar(val body:Seq[Paragraph]) extends Text { override def toHtml = stag_("p", body) }
240 case class Styled(val style:Style, val body:Seq[Text]) extends Text {
241 override def toHtml =
243 case Underline => tag_("u", body)
244 case TT => tag_("tt", body)
245 case Italic => tag_("i", body)
246 case Strikethrough => tag_("strike", body)
247 case Superscript => tag_("sup", body)
248 case Subscript => tag_("sub", body)
249 case Bold => tag_("b", body)
250 case Highlight => "<span class=highlight>"+mapToHtml(body)+"</span>"
253 case class Command(val command:String, val body:Seq[Text]) extends Text {
254 override def toHtml =
257 case "url" => "<tt>"+link(mapToHtml(body),body)+"</tt>"
258 case "WiX" => "W<span style='vertical-align:-20%'>I</span>X"
259 case "TeX" => "T<span style='vertical-align:-20%'>E</span>X"
260 case "red" => "<font color=red>"+mapToHtml(body)+"</font>"
261 case "orange" => "<font color=orange>"+mapToHtml(body)+"</font>"
262 case "green" => "<font color=green>"+mapToHtml(body)+"</font>"
263 case "sc" => "<sc>"+mapToHtml(body)+"</sc>"
264 case "image" => "<img src='"+mapToHtml(body)+"'/>"
265 case "imagec" => "<center><img src='"+mapToHtml(body)+"'/></center>"
266 case "image2" => "<img width=180px src='"+mapToHtml(body)+"'/>"
267 case "image3" => "<img width=200px src='"+mapToHtml(body)+"'/>"
268 case "image4" => "<center><img width=550px src='"+mapToHtml(body)+"'/></center>"
269 case "warn" => "\n<div class=warn>\n<table border=0 cellpadding=5px>\n"+
270 "<tr><td valign=top><image src='"+Html.warnIconBase64+"'></td>\n"+
273 "</td></tr></table></div>\n"
274 case "announce" => "\n<div class=announce>\n<table border=0 cellpadding=5px>\n"+
275 "<tr><td valign=top></td>\n"+
278 "</td></tr></table></div>\n"
279 case "br" => "\n<br/>\n"
280 case "alpha" => "α"
281 case "beta" => "β"
282 case "gamma" => "γ"
283 case "delta" => "δ"
284 case "epsilon" => "ε"
285 case "eta" => "η"
286 case "theta" => "θ"
287 case "kappa" => "κ"
288 case "lambda" => "λ"
289 case "mu" => "μ"
290 case "nu" => "ν"
291 case "pi" => "π"
292 case "rho" => "ρ"
293 // TO DO: integrate stixfonts stuff
294 case "cent" => "½"
295 case "euro" => "€"
298 val x = mapToHtml(body)
299 if (x.charAt(x.length-1) == '1') x+"<sup>"+"st"+"</sup>"
300 else if (x.charAt(x.length-1) == '2') x+"<sup>"+"nd"+"</sup>"
301 else if (x.charAt(x.length-1) == '3') x+"<sup>"+"rd"+"</sup>"
302 else x+"<sup>"+"th"+"</sup>"
304 // TO DO: use "unicode vulgar fractions" here
305 // directional quotes: see http://www.dwheeler.com/essays/quotes-in-html.html
306 /* u'1/2' : u'\u00BD',
307 // u'1/4' : u'\u00BC',
308 // u'3/4' : u'\u00BE',
309 // u'1/3' : u'\u2153',
310 // u'2/3' : u'\u2154',
311 // u'1/5' : u'\u2155',
312 // u'2/5' : u'\u2156',
313 // u'3/5' : u'\u2157',
314 // u'4/5' : u'\u2158',
315 // u'1/6' : u'\u2159',
316 // u'5/6' : u'\u215A',
317 // u'1/8' : u'\u215B',
318 // u'3/8' : u'\u215C',
319 // u'5/8' : u'\u215D',
320 // u'7/8' : u'\u215E',
322 case "fraction" => "<sup>"++body(0).toHtml++"</sup>"++"/"++"<sub>"++body(1).toHtml++"</sub>"
323 case "rfc" => "<tt><a href=http://tools.ietf.org/html/rfc"+mapToHtml(body)+">RFC"+mapToHtml(body)+"</a></tt>"
324 case "keystroke:command" => "⌘"
325 case "keystroke:shift" => "⇧"
326 case "keystroke:option" => "⌥"
327 case "keystroke:control" => "⌃"
328 case "keystroke:capslock" => "⇪"
329 case "keystroke:apple" => ""
330 case _ => throw new RuntimeException("unsupported command " + command)
335 abstract class Glyph extends ToHtml
336 case object Euro extends Glyph { override def toHtml = "€" }
337 case object CircleR extends Glyph { override def toHtml = "¢" }
338 case object CircleC extends Glyph { override def toHtml = "®" }
339 case object TradeMark extends Glyph { override def toHtml = "™" }
340 case object ServiceMark extends Glyph { override def toHtml = "™" }
341 case object Emdash extends Glyph { override def toHtml = "—" }
342 case object Ellipsis extends Glyph { override def toHtml = "…" /* &cdots;? */ }
343 case object Cent extends Glyph { override def toHtml = "½" }
344 case object Daggar extends Glyph { override def toHtml = "†" }
345 case object DoubleDaggar extends Glyph { override def toHtml = "‡" }
346 case object Clover extends Glyph { override def toHtml = "⌘" }
347 case object Flat extends Glyph { override def toHtml = "⋖" }
348 case object Natural extends Glyph { override def toHtml = "⋗" }
349 case object Sharp extends Glyph { override def toHtml = "⋘" }
350 case object CheckMark extends Glyph { override def toHtml = "✓" }
351 case object XMark extends Glyph { override def toHtml = "✗" }
352 case object LeftArrow extends Glyph { override def toHtml = "←" }
353 case object RightArrow extends Glyph { override def toHtml = "→" }
354 case object DoubleLeftArrow extends Glyph { override def toHtml = "&#;" /* FIXME */ }
355 case object DoubleRightArrow extends Glyph { override def toHtml = "&#;" /* FIXME */ }
356 case object DoubleLeftRightArrow extends Glyph { override def toHtml = "&#;" /* FIXME */ }
357 case object LeftRightArrow extends Glyph { override def toHtml = "&#;" /* FIXME */ }
358 case object Degree extends Glyph { override def toHtml = "&#;" /* FIXME */ }
360 class Login(val user:String, val password:Option[String]) {
361 override def toString =
364 case Some(x) => user+":"+urlEscape(x) }
368 case class URLPath(val path:String) extends URL { override def toString = path }
369 case class URLEmail(val user:String, val host:Host) extends URL { override def toString = "mailto:"+user+"@"+host }
370 case class URLNormal(val method:String,
371 val login:Option[Login],
373 val port:Option[Int],
375 val ref:Option[String]) extends URL {
376 override def toString =
380 case Some(log) => log+"@" })+
387 case Some(x) => "#"+x })
393 case class HostIP(val ip0:Int, val ip1:Int, val ip2:Int, val ip3:Int) extends Host {
394 override def toString = ip0+"."+ip1+"."+ip2+"."+ip3 }
395 case class HostDNS(val parts:Seq[String]) extends Host {
396 override def toString = joinStrings(parts, ".") }