7912eb303916d7d30db94a90c6879a6226fcbff8
[wix.git] / src / Doc.scala
1 import edu.berkeley.sbp.scala._
2
3 import Html.mapToHtml
4 import Html.joinStrings
5 import Html.{urlEscape,htmlEscape}
6 import Html.{pre,stag,tag,stag_,tag_,stag0,link}
7
8 object Doc {
9
10   def concatMap[A,B](f: A => Seq[B], s:Seq[A]) : Seq[B] =
11     concat(s.map(f))
12
13   def concat[A](s:Seq[Seq[A]]) : Seq[A] =
14     s.foldLeft(Seq[A]())(_ ++ _)
15
16   def docFromTree(t:Tree) : Doc =
17     t match { case Tree(_,Seq(_,Tree(_,a,_)),_) => new Doc(new Header(), a.map(sectionFromTree)) }
18
19   def sectionFromTree(t:Tree) : Section =
20     t match {
21       case Tree("Section", seq,_) =>
22         seq(0) match {
23           case Tree("SectionHeader", Seq(Tree("=",e,_),c),_) =>
24             new Section(e.length-1, textSequenceFromTree(c), paragraphsFromTrees(seq.tail))
25         }
26     }
27
28   def textSequenceFromTree (t:Tree) : Seq[Text] =
29     t match {
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)
67     }
68
69   def hostFromTree(t:Tree) : Host =
70     t match {
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) }))
75     }
76
77   def urlFromTree(t:Tree) : URL =
78     t match {
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("")(_ + _))
82     }
83
84   def urlFromTrees(t:Seq[Tree]) : URL =
85     t match {
86       case Seq(Tree(_,method,_), login, host, port, rest @_*) =>
87         new URLNormal(stringFromTrees(method),
88                       None,
89                       hostFromTree(host),
90                       port match { case Tree("Port",port,_) => {
91                                      val q = stringFromTrees(port)
92                                      if (q.equals("")) None else Some(java.lang.Integer.parseInt(q))
93                                    }
94                                    case _ => None },
95                       rest match { case Seq(Tree("Path",p,_), x@_*) => p.map(fromUrlChar).foldLeft("")(_ + _)
96                                    case _ => "" },
97                       rest match { case Seq(_ , Tree("Path",r,_), x@_*) => Some(stringFromTrees(r))
98                                    case _ => None })
99     }
100
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 =
104     t match {
105       case Tree("%", Seq(Tree(a,Seq(),_),Tree(b,Seq(),_)),_) => "%"+a+b
106       case Tree(x,y,_) =>
107         if (x.length==1) x
108         else throw new RuntimeException("could not parse as URL char: " + t)
109     }
110
111   def paragraphsFromTrees(ts:Seq[Tree]) : Seq[Paragraph] =
112     consolidate(concatMap(paragraphsFromTree,ts))
113
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)
127     })
128
129   def unverbate (t:Tree) : String =
130     t match {
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
134     }
135
136   def unindent (t:Tree,v:String) : String =
137     t match {
138       case Tree("I", indent,_) => unindent_(indent.length+1, v)
139     }
140
141   private def unindent_ (i:Int,v:String) : String =
142     if (v.length==0)    ""
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))
145
146   private def drop_(n:Int, x:String) : String = {
147     val x_ : Seq[Char] = x;
148     if (n==0) x
149     else (x_ match {
150       case Seq('\n', r@_*) => x
151       case Seq()           => ""
152       case Seq(a, b@_*)    => drop_(n-1, x.substring(1))
153     })
154   }
155
156   def consolidate(x:Seq[Paragraph]) : Seq[Paragraph] =
157     x match {
158       case Seq() => Seq()
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)
165     }
166
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("")(_ + _)
173
174 }
175
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"+
180      "<html>\n"+
181      "<head>\n"+
182      Html.style+
183      // FIXME: title tag
184      "</head>\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"+
188      mapToHtml(sections)+
189      "<br><br>\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"+
196      "</body></html>"
197 }
198
199 class Header()
200
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)) }
203
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"+
215       "<td class=warn>\n"+
216       mapToHtml(body)+
217       "</td></tr></table>\n"
218   }
219
220 abstract class Style
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
229
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 = "&#8220;"+mapToHtml(body)+"&#8221;" }
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 =
243       style match {
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>"
252       }
253   }
254   case class Command(val command:String, val body:Seq[Text]) extends Text {
255     override def toHtml = 
256       command match {
257         case "comment" =>     ""
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"+
272                               "<td class=warn>\n"+
273                               mapToHtml(body)+
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"+
277                               "<td class=warn>\n"+
278                               mapToHtml(body)+
279                               "</td></tr></table></div>\n"
280         case "br"      =>     "\n<br/>\n"
281         case "alpha"   =>     "&#x03B1;"
282         case "beta"    =>     "&#x03B2;"
283         case "gamma"   =>     "&#x03B3;"
284         case "delta"   =>     "&#x03B4;"
285         case "epsilon" =>     "&#x03B5;"
286         case "eta"     =>     "&#x03B7;"
287         case "theta"   =>     "&#x03B8;"
288         case "kappa"   =>     "&#x03Ba;"
289         case "lambda"  =>     "&#x03Bb;"
290         case "mu"      =>     "&#x03Bc;"
291         case "nu"      =>     "&#x03Bd;"
292         case "pi"      =>     "&#x03c0;"
293         case "rho"     =>     "&#x03c1;"
294         // TO DO: integrate stixfonts stuff
295         case "cent"    =>     "&#189;"
296         case "euro"    =>     "&#8364;"
297         // gross hack
298         case "ordinal"     => {
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>"
304         }
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',
322         */
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"  => "&#x2318;"
326         case "keystroke:shift"    => "&#x21E7;"
327         case "keystroke:option"   => "&#x2325;"
328         case "keystroke:control"  => "&#x2303;"
329         case "keystroke:capslock" => "&#x21EA;"
330         case "keystroke:apple"    => "&#xF8FF;"
331         case _                    => throw new RuntimeException("unsupported command " + command)
332       }
333   }
334
335
336 abstract class Glyph extends ToHtml
337   case object Euro extends Glyph { override def toHtml = "&#8364;" }
338   case object CircleR extends Glyph { override def toHtml = "&#162;" }
339   case object CircleC extends Glyph { override def toHtml = "&#174;" }
340   case object TradeMark extends Glyph { override def toHtml = "&#8482;" }
341   case object ServiceMark extends Glyph { override def toHtml = "&#8482;" }
342   case object Endash extends Glyph { override def toHtml = "&ndash;" }
343   case object Emdash extends Glyph { override def toHtml = "&mdash;" }
344   case object Ellipsis extends Glyph { override def toHtml = "&#0133;"  /* &cdots;? */ }
345   case object Cent extends Glyph { override def toHtml = "&#189;" }
346   case object Daggar extends Glyph { override def toHtml = "&#8224;" }
347   case object DoubleDaggar extends Glyph { override def toHtml = "&#8225;" }
348   case object Clover extends Glyph { override def toHtml = "&#8984;" }
349   case object Flat extends Glyph { override def toHtml = "&#8918;" }
350   case object Natural extends Glyph { override def toHtml = "&#8919;" }
351   case object Sharp extends Glyph { override def toHtml = "&#8920;" }
352   case object CheckMark extends Glyph { override def toHtml = "&#10003;" }
353   case object XMark extends Glyph { override def toHtml = "&#10007;" }
354   case object LeftArrow extends Glyph { override def toHtml = "&larr;" }
355   case object RightArrow extends Glyph { override def toHtml = "&rarr;" }
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 */ }
361
362 class Login(val user:String, val password:Option[String]) {
363   override def toString =
364     password match {
365       case None    => user
366       case Some(x) => user+":"+urlEscape(x) }
367 }
368
369 abstract class URL
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],
374                        val host:Host,
375                        val port:Option[Int],
376                        val path:String,
377                        val ref:Option[String]) extends URL {
378     override def toString =
379      method+"://"+
380      (login match {
381        case None => ""
382        case Some(log) => log+"@" })+
383      host+
384      "/"+
385      path+
386      (ref match {
387        case None => ""
388        case Some("") => ""
389        case Some(x) => "#"+x })
390
391   }
392
393
394 abstract class Host
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, ".") }
399
400
401
402
403
404
405
406
407
408
409
410
411
412