import edu.berkeley.sbp.scala._ import Html.mapToHtml import Html.joinStrings import Html.{urlEscape,htmlEscape} import Html.{pre,stag,tag,stag_,tag_,stag0,link} object Doc { def concatMap[A,B](f: A => Seq[B], s:Seq[A]) : Seq[B] = concat(s.map(f)) def concat[A](s:Seq[Seq[A]]) : Seq[A] = s.foldLeft(Seq[A]())(_ ++ _) def docFromTree(t:Tree) : Doc = t match { case Tree(_,Seq(_,Tree(_,a,_)),_) => new Doc(new Header(), a.map(sectionFromTree)) } def sectionFromTree(t:Tree) : Section = t match { case Tree("Section", seq,_) => seq(0) match { case Tree("SectionHeader", Seq(Tree("=",e,_),c),_) => new Section(e.length-1, textSequenceFromTree(c), paragraphsFromTrees(seq.tail)) } } def textSequenceFromTree (t:Tree) : Seq[Text] = t match { case Tree("Word", chars ,_) => Seq(new Chars(stringFromTrees(chars))) case Tree("Ordinal", x ,_) => Seq(new Command("ordinal", Seq(new Chars(stringFromTrees(x))))) case Tree("Fraction", Seq(n,d) ,_) => Seq(new Command("fraction",Seq(new Chars(stringFromTree(n)), new Chars(stringFromTree(d))))) case Tree("WS", _ ,_) => Seq(WS) case Tree("Quotes", Seq(x) ,_) => Seq(new Quotes(textSequenceFromTree(x))) case Tree("Pars", y ,_) => Seq(new SubPar(paragraphsFromTrees(y))) case Tree("Command", Seq(x,y) ,_) => Seq(new Command(stringFromTree(x), textSequenceFromTree(y))) case Tree("Command", Seq(x) ,_) => Seq(new Command(stringFromTree(x), Seq())) case Tree("Link", Seq(text,link) ,_) => Seq(new Link(urlFromTree(link), textSequenceFromTree(text))) case Tree("Footnote", x ,_) => Seq(new Footnote(concatMap(textSequenceFromTree,x))) case Tree("Keyword", x ,_) => Seq(new Keyword(concatMap(textSequenceFromTree,x))) case Tree("Math", x ,_) => Seq(new Math(stringFromTrees(x))) case Tree("Italic", Seq(x) ,_) => Seq(new Styled(Italic , textSequenceFromTree(x))) case Tree("Bold", Seq(x) ,_) => Seq(new Styled(Bold , textSequenceFromTree(x))) case Tree("Highlight", Seq(x) ,_) => Seq(new Styled(Highlight , textSequenceFromTree(x))) case Tree("TT", x ,_) => Seq(new Styled(TT , concatMap(textSequenceFromTree,x))) case Tree("Strikethrough", x ,_) => Seq(new Styled(Strikethrough , concatMap(textSequenceFromTree,x))) case Tree("Superscript", x ,_) => Seq(new Styled(Superscript , concatMap(textSequenceFromTree,x))) case Tree("Subscript", x ,_) => Seq(new Styled(Subscript , concatMap(textSequenceFromTree,x))) case Tree("Underline", x ,_) => Seq(new Styled(Underline , concatMap(textSequenceFromTree,x))) case Tree("(e)", _ ,_) => Seq(new GlyphText(Euro)) case Tree("(r)", _ ,_) => Seq(new GlyphText(CircleR)) case Tree("(c)", _ ,_) => Seq(new GlyphText(CircleC)) case Tree("(tm)", _ ,_) => Seq(new GlyphText(TradeMark)) case Tree("--", _ ,_) => Seq(new GlyphText(Endash)) case Tree("---", _ ,_) => Seq(new GlyphText(Emdash)) case Tree("<-", _ ,_) => Seq(new GlyphText(LeftArrow)) case Tree("<=", _ ,_) => Seq(new GlyphText(DoubleLeftArrow)) case Tree("=>", _ ,_) => Seq(new GlyphText(DoubleRightArrow)) case Tree("<=>", _ ,_) => Seq(new GlyphText(DoubleLeftRightArrow)) case Tree("<->", _ ,_) => Seq(new GlyphText(LeftRightArrow)) case Tree("^o", _ ,_) => Seq(new GlyphText(Degree)) case Tree("...", _ ,_) => Seq(new GlyphText(Ellipsis)) case Tree("Text", ts ,_) => concat(ts.map(textSequenceFromTree)) case Tree("", Seq() ,_) => Seq() case t => throw new RuntimeException("unable to create [Text] from " + t) } def hostFromTree(t:Tree) : Host = t match { case Tree("IP", Seq(Tree(_,a,_),Tree(_,b,_),Tree(_,c,_),Tree(_,d,_)),_) => new HostIP(intFromTrees(a), intFromTrees(b), intFromTrees(c), intFromTrees(d)) case Tree("DNS", parts,_) => new HostDNS(parts.map( (t:Tree) => t match { case Tree(_, c,_) => stringFromTrees(c) })) } def urlFromTree(t:Tree) : URL = t match { case Tree("URL", stuff,_) => urlFromTrees(stuff) case Tree("Email", Seq(Tree("username", un,_),host),_) => new URLEmail(stringFromTrees(un), hostFromTree(host)) case Tree("Path",stuff,_) => new URLPath(stuff.map(fromUrlChar).foldLeft("")(_ + _)) } def urlFromTrees(t:Seq[Tree]) : URL = t match { case Seq(Tree(_,method,_), login, host, port, rest @_*) => new URLNormal(stringFromTrees(method), None, hostFromTree(host), port match { case Tree("Port",port,_) => { val q = stringFromTrees(port) if (q.equals("")) None else Some(java.lang.Integer.parseInt(q)) } case _ => None }, rest match { case Seq(Tree("Path",p,_), x@_*) => p.map(fromUrlChar).foldLeft("")(_ + _) case _ => "" }, rest match { case Seq(_ , Tree("Path",r,_), x@_*) => Some(stringFromTrees(r)) case _ => None }) } //fromUrlChar (Tree "%" [(Tree a [] _),(Tree b [] _)] _) = chr $ (fst $ head $ readHex (a++b)) // FIXME: problem here is the "/" vs "%2F" issue, so we "leave urls urlencoded" def fromUrlChar(t:Tree) : String = t match { case Tree("%", Seq(Tree(a,Seq(),_),Tree(b,Seq(),_)),_) => "%"+a+b case Tree(x,y,_) => if (x.length==1) x else throw new RuntimeException("could not parse as URL char: " + t) } def paragraphsFromTrees(ts:Seq[Tree]) : Seq[Paragraph] = consolidate(concatMap(paragraphsFromTree,ts)) def paragraphsFromTree(t:Tree) : Seq[Paragraph] = consolidate (t match { case Tree("Verbatim", Seq(indent,v),_) => Seq(new P( Seq(new Verbatim(unindent(indent,unverbate(v)))))) case Tree("TextParagraph", Seq(Tree(_,text,_)),_) => Seq(new P(concatMap(textSequenceFromTree,text))) case Tree("Pars", pars ,_) => concatMap(paragraphsFromTree,pars) case Tree("HR", _ ,_) => Seq(HR) case Tree("OL", a ,_) => Seq(new OL(a.map( (t:Tree) => t match { case Tree("LI",x,_) => paragraphsFromTrees(x)}))) case Tree("UL", a ,_) => Seq(new UL(a.map( (t:Tree) => t match { case Tree("LI",x,_) => paragraphsFromTrees(x)}))) case Tree("", _ ,_) => Seq() case Tree("Blockquote", pars ,_) => Seq(Blockquote(paragraphsFromTrees(pars))) case _ => throw new RuntimeException("unable to create [Paragraph] from " + t) }) def unverbate (t:Tree) : String = t match { case Tree("Verbatim",x,_) => x.map(unverbate).foldLeft("")(_ + _) case Tree("VerbatimBrace",Seq(x,y),_) => unverbate(x)+" "+unverbate(y) case Tree(t,Seq(),_) => t } def unindent (t:Tree,v:String) : String = t match { case Tree("I", indent,_) => unindent_(indent.length+1, v) } private def unindent_ (i:Int,v:String) : String = if (v.length==0) "" else if (v.charAt(0) == '\n') "\n"+unindent_(i, drop_(i, v.substring(1))) else v.charAt(0)+unindent_(i, v.substring(1)) private def drop_(n:Int, x:String) : String = { val x_ : Seq[Char] = x; if (n==0) x else (x_ match { case Seq('\n', r@_*) => x case Seq() => "" case Seq(a, b@_*) => drop_(n-1, x.substring(1)) }) } def consolidate(x:Seq[Paragraph]) : Seq[Paragraph] = x match { case Seq() => Seq() case Seq(a) => Seq(a) case Seq(OL(Seq()), x@_*) => consolidate(x) case Seq(UL(Seq()), x@_*) => consolidate(x) case Seq(OL(a), OL(b), x@_*) => consolidate(Seq(OL(a++b))++x) case Seq(UL(a), UL(b), x@_*) => consolidate(Seq(UL(a++b))++x) case Seq(a, b @_*) => Seq(a)++consolidate(b) } def intFromTrees(t:Seq[Tree]) : Int = java.lang.Integer.parseInt(stringFromTrees(t)) def stringFromTree(t:Tree) : String = t match { case Tree(h,c,_) => h++concatMap(stringFromTree,c) } def stringFromTrees(ts:Seq[Tree]) : String = ts.map(stringFromTree).foldLeft("")(_ + _) } class Doc (val header:Header, val sections:Seq[Section]) extends ToHtml { override def toHtml = "\n"+ "\n\n"+ "\n"+ "\n"+ Html.style+ // FIXME: title tag "\n"+ "\n"+ // tell jsmath we will escape stuff manually Html.jsMath+ // FIXME: only put this in if math appears on the page "
\n"+ mapToHtml(sections)+ "

\n"+ ""+ "\n"+ "
\n"+ "" } class Header() class Section (val level:Int, val header:Seq[Text], val paragraphs:Seq[Paragraph]) extends ToHtml { def toHtml = "\n\n"+(mapToHtml(header))+"\n\n"+(mapToHtml(paragraphs)) } abstract class Paragraph extends ToHtml case class P (val body :Seq[Text] ) extends Paragraph { override def toHtml = stag_("p",body) } case object HR extends Paragraph { override def toHtml = stag("hr") } case class OL (val items:Seq[Seq[Paragraph]] ) extends Paragraph { override def toHtml = stag0("ol", items.map( (s:Seq[Paragraph]) => stag_("li", s) ).foldLeft("")(_ + _)) } case class UL (val items:Seq[Seq[Paragraph]] ) extends Paragraph { override def toHtml = stag0("ul", items.map( (s:Seq[Paragraph]) => stag_("li", s) ).foldLeft("")(_ + _)) } case class Blockquote (val body :Seq[Paragraph] ) extends Paragraph { override def toHtml = "\n\n"+ "\n"+ "
\n"+ mapToHtml(body)+ "
\n" } abstract class Style case object TT extends Style case object Underline extends Style case object Superscript extends Style case object Subscript extends Style case object Strikethrough extends Style case object Italic extends Style case object Bold extends Style case object Highlight extends Style abstract class Text extends ToHtml case object WS extends Text { def toHtml = " " } case class Chars(val body:String) extends Text { def toHtml = htmlEscape(body) } case class Quotes(val body:Seq[Text]) extends Text { def toHtml = "“"+mapToHtml(body)+"”" } case class GlyphText(val body:Glyph) extends Text { override def toHtml = body.toHtml } case class Math(val body:String) extends Text { override def toHtml = "" +body+ "" } case class Verbatim(val body:String) extends Text { override def toHtml = pre(body) } case class Link(val url:URL, val body:Seq[Text]) extends Text { override def toHtml = link(url.toString, body) } case class Footnote(val body:Seq[Text]) extends Text { override def toHtml = throw new Exception() } case class Keyword(val body:Seq[Text]) extends Text { override def toHtml = tag_("tt", body) } case class SubPar(val body:Seq[Paragraph]) extends Text { override def toHtml = stag_("p", body) } case class Styled(val style:Style, val body:Seq[Text]) extends Text { override def toHtml = style match { case Underline => tag_("u", body) case TT => tag_("tt", body) case Italic => tag_("i", body) case Strikethrough => tag_("strike", body) case Superscript => tag_("sup", body) case Subscript => tag_("sub", body) case Bold => tag_("b", body) case Highlight => ""+mapToHtml(body)+"" } } case class Command(val command:String, val body:Seq[Text]) extends Text { override def toHtml = command match { case "comment" => "" case "url" => ""+link(mapToHtml(body),body)+"" case "WiX" => "WIX" case "TeX" => "TEX" case "red" => ""+mapToHtml(body)+"" case "orange" => ""+mapToHtml(body)+"" case "green" => ""+mapToHtml(body)+"" case "sc" => ""+mapToHtml(body)+"" case "image" => "" case "imagec" => "
" case "image2" => "" case "image3" => "" case "image4" => "
" case "warn" => "\n
\n\n"+ "\n"+ "
\n"+ mapToHtml(body)+ "
\n" case "announce" => "\n
\n\n"+ "\n"+ "
\n"+ mapToHtml(body)+ "
\n" case "br" => "\n
\n" case "alpha" => "α" case "beta" => "β" case "gamma" => "γ" case "delta" => "δ" case "epsilon" => "ε" case "eta" => "η" case "theta" => "θ" case "kappa" => "κ" case "lambda" => "λ" case "mu" => "μ" case "nu" => "ν" case "pi" => "π" case "rho" => "ρ" // TO DO: integrate stixfonts stuff case "cent" => "½" case "euro" => "€" // gross hack case "ordinal" => { val x = mapToHtml(body) if (x.charAt(x.length-1) == '1') x+""+"st"+"" else if (x.charAt(x.length-1) == '2') x+""+"nd"+"" else if (x.charAt(x.length-1) == '3') x+""+"rd"+"" else x+""+"th"+"" } // TO DO: use "unicode vulgar fractions" here // directional quotes: see http://www.dwheeler.com/essays/quotes-in-html.html /* u'1/2' : u'\u00BD', // u'1/4' : u'\u00BC', // u'3/4' : u'\u00BE', // u'1/3' : u'\u2153', // u'2/3' : u'\u2154', // u'1/5' : u'\u2155', // u'2/5' : u'\u2156', // u'3/5' : u'\u2157', // u'4/5' : u'\u2158', // u'1/6' : u'\u2159', // u'5/6' : u'\u215A', // u'1/8' : u'\u215B', // u'3/8' : u'\u215C', // u'5/8' : u'\u215D', // u'7/8' : u'\u215E', */ case "fraction" => ""++body(0).toHtml++""++"/"++""++body(1).toHtml++"" case "rfc" => "RFC"+mapToHtml(body)+"" case "keystroke:command" => "⌘" case "keystroke:shift" => "⇧" case "keystroke:option" => "⌥" case "keystroke:control" => "⌃" case "keystroke:capslock" => "⇪" case "keystroke:apple" => "" case _ => throw new RuntimeException("unsupported command " + command) } } abstract class Glyph extends ToHtml case object Euro extends Glyph { override def toHtml = "€" } case object CircleR extends Glyph { override def toHtml = "¢" } case object CircleC extends Glyph { override def toHtml = "®" } case object TradeMark extends Glyph { override def toHtml = "™" } case object ServiceMark extends Glyph { override def toHtml = "™" } case object Endash extends Glyph { override def toHtml = "–" } case object Emdash extends Glyph { override def toHtml = "—" } case object Ellipsis extends Glyph { override def toHtml = "…" /* &cdots;? */ } case object Cent extends Glyph { override def toHtml = "½" } case object Daggar extends Glyph { override def toHtml = "†" } case object DoubleDaggar extends Glyph { override def toHtml = "‡" } case object Clover extends Glyph { override def toHtml = "⌘" } case object Flat extends Glyph { override def toHtml = "⋖" } case object Natural extends Glyph { override def toHtml = "⋗" } case object Sharp extends Glyph { override def toHtml = "⋘" } case object CheckMark extends Glyph { override def toHtml = "✓" } case object XMark extends Glyph { override def toHtml = "✗" } case object LeftArrow extends Glyph { override def toHtml = "←" } case object RightArrow extends Glyph { override def toHtml = "→" } case object DoubleLeftArrow extends Glyph { override def toHtml = "&#;" /* FIXME */ } case object DoubleRightArrow extends Glyph { override def toHtml = "&#;" /* FIXME */ } case object DoubleLeftRightArrow extends Glyph { override def toHtml = "&#;" /* FIXME */ } case object LeftRightArrow extends Glyph { override def toHtml = "&#;" /* FIXME */ } case object Degree extends Glyph { override def toHtml = "&#;" /* FIXME */ } class Login(val user:String, val password:Option[String]) { override def toString = password match { case None => user case Some(x) => user+":"+urlEscape(x) } } abstract class URL case class URLPath(val path:String) extends URL { override def toString = path } case class URLEmail(val user:String, val host:Host) extends URL { override def toString = "mailto:"+user+"@"+host } case class URLNormal(val method:String, val login:Option[Login], val host:Host, val port:Option[Int], val path:String, val ref:Option[String]) extends URL { override def toString = method+"://"+ (login match { case None => "" case Some(log) => log+"@" })+ host+ "/"+ path+ (ref match { case None => "" case Some("") => "" case Some(x) => "#"+x }) } abstract class Host case class HostIP(val ip0:Int, val ip1:Int, val ip2:Int, val ip3:Int) extends Host { override def toString = ip0+"."+ip1+"."+ip2+"."+ip3 } case class HostDNS(val parts:Seq[String]) extends Host { override def toString = joinStrings(parts, ".") }