1 package org.blackquill.engine
3 // BlackQuill Copyright (C) 2013 set.minami<set.minami@gmail.com>
4 // License MIT see also LISENCE.txt
7 import org.apache.commons.logging._
8 import scala.collection.immutable.List
9 import scala.collection.mutable.LinkedHashMap
10 import scala.collection.mutable.HashMap
11 import scala.collection.mutable.Set
12 import scala.collection.mutable.Stack
13 import scala.collection.mutable.ListMap
14 import scala.collection.SortedSet
15 import scala.util.matching.Regex
16 import scala.util.control.Breaks.{break,breakable}
19 import org.blackquill.engine._
20 import org.blackquill.io.FileIO
24 private val log:Log = LogFactory.getLog(classOf[BQParser])
26 private var urlDefMap = new HashMap[String,Tuple5[String,String,String,String,String]]
27 private var footnoteMap = new LinkedHashMap[String,Tuple2[String,String]]
28 private var headerMap = List[Tuple4[Int,Int,String,String]]()
30 private val Syntax = LinkedHashMap(
32 """^(.*?)(([^(?:\\,)]+?\\,(:(.+?)\\,)+)+)(.*?)$$""" -> ("dl", wordDefinition _),
33 """(.*)\\,~{3,}(?:\{(.+?)\})?(\\,.+\\,)~{3,}\\,(.*)""" -> ("code", fencedCode _),
34 """^(.*?)(?:\[(.+?)\](?:\{(.+?)\})?\\,)?((\|.+?)+?\|)\\,((\|:?\-{3,}:?)+?\|)\\,(((\|.+?\|?)+?\\,)+?)\\,(.*?)$$"""
35 -> ("table",surroundTableTAG _),
36 "^(.*?)`(.*)" -> ("code",surroundByCodeTAG _),
37 "^(.*)<([\\w\\d\\.\\-\\_\\+]+?)@([\\w\\d\\.\\-\\_\\+]+?)>(.*)" -> ("a", autoMailLink _),
38 "^(.*)<((?:https?|ftp):\\/\\/[\\w\\d\\.\\/]+?)>(.*)$$" -> ("a",autoURLLink _),
39 """^(.*)\[\^(.+?)\](.*)$$""" -> ("sup",insertFootnote _),
40 """(.*)\*\[(.*?)\]\{(#?[\w\d]+?)?(/#?[\w\d]+?)?(\(\+?(.*?)?(\|\+?(.*?))?\))?(\[(.*?)\])?\}(.*)"""
41 -> ("span",colorPen _),
42 "^(.*)!\\[(.+?)\\]\\[(.*?)\\](?:\\{(.+?)\\})?(.*)$$" -> ("img",referenceExpander _),
43 "^(.*)\\[(.+?)\\]\\[(.*?)\\](?:\\{(.+?)\\})?(.*)$$" -> ("a",referenceExpander _),
44 "^(.*?)!\\[(.*?)\\]\\((.+?)\\x20*?(?:\"(.+?)\")?(?:\\x20+?(\\d+?%?)?x(\\d+?%?)?)?\\)(?:\\{(.+?)\\})?(.*)$$"
45 -> ("img", putImgTAG _),
46 "^(.*?)\\[(.*?)\\]\\((.+?)\\x20*?(?:\"(.+?)\")?\\)(?:\\{(.+?)\\})?(.*?)$$" -> ("a", surroundaHrefTAG _),
47 "^(.*?\\\\,)(((?:\\x20{4,}|\\t+)(.*?\\\\,))+)(.*?)$$" -> ("code",surroundByPreCodeTAG _),
48 "^(.*?\\\\,)((>.*(?:\\\\,))+?)(.*?)$$" -> ("blockquote",surroundByBlockquoteTAG _),
49 "^(.*?)(((?:\\x20{4,}|\\t+)\\d+?\\.\\x20.+?\\\\,)+)(.*?)$$" -> ("ol",surroundByListTAG _),
50 "^(.*?)(((?:\\x20{4,}|\\t+)(?:\\*|\\+|\\-)\\x20.+?\\\\,)+)(.*?)$$" -> ("ul",surroundByListTAG _),
51 "^(.*?)(#{1,6})\\x20(.+?)(\\x20#{1,6}?(?:\\{(.*?)\\}))?\\\\,(.*?)$$" -> ("h",surroundByHeadTAG _),
52 "^(.*\\\\,)(.*?)(?:\\{(.+?)\\})?\\\\,(\\-+|=+)\\x20*\\\\,(.*?)$$" -> ("h",surroundByHeadTAGUnderlineStyle _),
53 """^(.*?)(\{toc(:.+?)?\})(.*)$$""" -> ("ul",generateTOC _),
54 "^(.*\\\\,)((?:\\-|\\*){3,}|(?:(?:\\-|\\*)\\x20){3,})(.*?)$$" -> ("hr",putHrTAG _),
55 "^(.*?)\\*\\*(.+?)\\*\\*(.*?)$$" -> ("strong",surroundByGeneralTAG _),
56 "^(.*?)\\*(.+?)\\*(.*?)$$" -> ("em",surroundByGeneralTAG _)
60 private def colorPen(doc:String, regex:String, TAG:String):String = {
61 lazy val fontSize = Map[Int,String](0 -> "medium", 1 -> "larger", 2 -> "large", 3 -> "x-large", 4 -> "xx-large",
62 -1 -> "smaller", -2 -> "small", -3 -> "x-small", -4 -> "xx-small")
63 lazy val fontWeight = Map(0 -> "normal", 1 -> "bolder", 2 -> "bold" ,-1 -> "light")
64 if(doc == ""){return ""}
65 val p = new Regex(regex, "before","content","fcolor","bcolor","fstyle","size","dummy","weight","face","ffamily","following")
66 val m = p findFirstMatchIn(doc)
69 val bef = m.get.group("before")
70 val fol = m.get.group("following")
72 if(Option(m.get.group("content")) != None){
73 val content = m.get.group("content")
74 val fgColor = if(Option(m.get.group("fcolor")) != None){" color:" + m.get.group("fcolor") + ";"}else{""}
75 val bgColor = if(Option(m.get.group("bcolor")) != None){" background-color:" + m.get.group("bcolor").tail + ";"}else{""}
77 val fSize = if(Option(m.get.group("size")) != None){
79 log info m.get.group("size")
80 " font-size:" + fontSize(m.get.group("size").toInt) + ";"
81 }catch{ case e:Exception => log info e;" font-size:" + fontSize(0) + ";"}
84 val fWeight = if(Option(m.get.group("weight")) != None){
86 " font-weight:" + fontWeight(m.get.group("weight").toInt)
87 }catch{ case e:Exception => log info e ;" font-weight:" + fontWeight(0) + ";"}
92 val fFace = if(Option(m.get.group("ffamily")) != None){
93 " font-family:" + m.get.group("ffamily").split(",").mkString("'", "','", "'") + ";"
96 return colorPen(bef, regex, TAG) +
97 s"""<span style="$fgColor$bgColor$fSize$fWeight$fFace">$content</span> """ +
98 colorPen(fol, regex, TAG)
100 return colorPen(bef, regex, TAG) + colorPen(fol, regex, TAG)
106 private def generateTOC(doc:String, regex:String, TAG:String):String = {
109 def _checkRange(start:Option[String],end:Option[String],default:Tuple2[Int,Int]):Tuple2[Int,Int] = {
110 val s = if(start != None && start.get.toInt >= default._1){start.get.toInt}else{default._1}
111 val e = if(end != None && end.get.toInt <= default._2){end.get.toInt}else{default._2}
116 val p = new Regex(regex, "before","toc","range","following")
117 val m = p findFirstMatchIn(doc)
120 val bef = m.get.group("before")
121 val fol = m.get.group("following")
122 val tocRange = m.get.group("toc")
123 if(Option(tocRange) != None){
124 val p2 = s":h?([${minmax._1}-${minmax._2}])?\\-h?([${minmax._1}-${minmax._2}])?".r
125 val range = p2 findFirstMatchIn(tocRange)
127 minmax = _checkRange(Option(range.get.group(1)),Option(range.get.group(2)),minmax)
129 log warn "SYNTAX ERROR:toc header setting"
134 val hList = makeHeaderMap(doc,minmax._1,minmax._2)
137 val link = if(e._3 != None){e._3.get}else{s"""${e._2}:${e._4}:${e._1}""" }
138 headerMap ::= (e._1,headSize,link.toString,e._4)
140 headerMap = headerMap.reverse
142 var i = headerMap.head._2
147 toc += """<ul style="list-style:none" >\\,"""
150 toc += """</ul>\\,""" * ulNest
153 toc += s"""<li><a href="#${h._3}" ><h${h._2}>${h._4}</h${h._2}></a></li>\\,"""
157 val table = """<header>\\,<ul style="list-style:none" id="toc"><nav>\\,""" + toc.mkString("") + "</nav></ul>\\,</header>"
159 return putHeaderID(bef,minmax._1,minmax._2) + table + putHeaderID(fol,minmax._1,minmax._2)
164 private def putHeaderID(doc:String,min:Int,max:Int):String ={
165 if(doc == ""){return ""}
167 for((e,i) <- doc.split("""\\,""").zipWithIndex){
168 val p = """<h(\d)\s*?>(.*?)</h\d>""".r
169 val m = p findFirstMatchIn(e)
173 val header = m.get.group(1).toInt
174 if( header >= min && header <= max){
175 val id = for(h<-headerMap if h._1 == i)yield{h._3}
176 text += s"""<h${header} id="${id.head}">${m.get.group(2)}</h$header>\\,"""
178 text += e + """\\,"""
180 }else{text += e + """\\,"""}
185 private def makeHeaderMap(doc:String,minH:Integer,maxH:Integer):List[Tuple4[Integer,Integer,Option[String],String]] = {
186 var headList = List[Tuple4[Integer,Integer,Option[String],String]]()
187 for((line,no) <- doc.split("""\\,""").zipWithIndex){
188 log info "line=" + line +" No=" + no
189 val p = """.*?(<h(\d)(.*?)\s*?>(.*?)</h\d>).*""".r
190 val m = p.findAllMatchIn(line)
193 val p2 = """<h(\d)(?:\s*?id=\"(.*?)\")?\s*?>(.*?)</h\d""".r
194 val m2 = p2.findFirstMatchIn(e.group(1))
195 val test = m2.get.group(1).toInt
196 val id = m2.get.group(2)
198 if(test >= minH && test <= maxH){
199 headList ::= (no, test ,Option(id),m2.get.group(3))
207 private def wordDefinition(doc:String, regex:String, TAG:String):String = {
209 if(doc == ""){return ""}
210 val p = new Regex(regex,"before","seq","word","defSeq","def","following")
211 val m = p.findFirstMatchIn(doc)
214 val bef = m.get.group("before")
215 val fol = m.get.group("following")
216 val p2 = """(((?:\\,)?.*?\\,)(:.+?\\,)+)+?""".r
217 log info "seq ->" + m.get.group("seq")
219 val m2 = p2.findAllMatchIn(m.get.group("seq"))
224 val p3 = """(?:\\,)?([^\\,]*?)\\,((:(.+?)\\,)+)""".r
225 val m3 = p3.findAllMatchIn(e.group(1))
228 dd += "<dt>" + e2.group(1) + "</dt>"
229 val p4 = """:(.+?)\\,""".r
230 val m4 = p4.findAllMatchIn(e2.group(2))
232 dd += "<dd>" + e3.group(1) + "</dd>\\,"
236 return wordDefinition(bef,regex,TAG) + "\\," +
237 s"""<$TAG>\\,$dd\\,</$TAG>""" +
238 wordDefinition(fol, regex,TAG)
242 private def insertFootnote(doc:String, regex:String, TAG:String):String = {
243 if(doc == ""){return ""}
245 val p = new Regex(regex,"before","footnote","following")
246 val m = p.findFirstMatchIn(doc)
248 val bef = m.get.group("before")
249 var fnote = m.get.group("footnote")
250 val fol = m.get.group("following")
251 if(footnoteMap.contains(fnote)){
252 val link = footnoteMap(fnote)._1
253 lazy val definition = footnoteMap(fnote)._2
255 return insertFootnote(bef,regex,TAG) + s"""<$TAG id=\"$fnote\"><a href=\"#$link\" style="text-decoration:none">[$fnote]</a></sup>""" +
256 insertFootnote(fol,regex,TAG)
258 log warn "Found lack of Footnotes."
264 private def gatheringFootnotesDefinition(doc:String):String = {
265 val p = new Regex("""^(.*)\[\^(.*?)\]:(.*?)\\,(.*)$$""","before","landMark","definition","following")
266 val m = p findFirstMatchIn(doc)
269 val bef = m.get.group("before")
270 val fol = m.get.group("following")
271 val lMark = m.get.group("landMark")
272 val define = m.get.group("definition")
274 if(!footnoteMap.contains(lMark)){
275 footnoteMap += (lMark->("footnote-"+lMark,define))
277 log warn s"FOUND FOOTNOTE DUPES! @ $lMark: Second definitions was ignored."
279 return gatheringFootnotesDefinition(bef) + gatheringFootnotesDefinition(fol)
284 private def insertFootnoteDefinitions(doc:LinkedHashMap[String,Tuple2[String,String]]):String = {
285 lazy val head = """<footer><nav>\,<h2 id="footnotes">Footnotes</h2><hr />\,<ul style="list-style:none">\,"""
287 for((key,t2) <- doc.toList.reverse)yield(s"""<li id="${t2._1}"><p><em>$key: </em>${t2._2}<a href="#$key">«</a></p>""")
288 lazy val tail = "</ul></nav></footer>"
289 if(contents.size > 0){
290 return head + contents.mkString("\\,") + tail
294 private def fencedCode(doc:String, regex:String, TAG:String):String = {
295 val p = new Regex(regex, "before", "SAttr", "inTAG", "following")
296 val m = p.findFirstMatchIn(doc)
299 val bef = m.get.group("before")
300 val fol = m.get.group("following")
301 val inCode = m.get.group("inTAG")
304 if(Option(m.get.group("SAttr")) != None){
305 specialAttr = decideClassOrStyle(doc,m.get.group("SAttr"))
308 return fencedCode(bef,regex,"code") +
309 s"<pre $specialAttr><$TAG>\\\\," + ltgtExpand(inCode) + s"</$TAG></pre>" +
310 fencedCode(fol,regex,TAG)
315 private def surroundTableTAG(doc:String, regex:String, TAG:String):String = {
316 def _normalize(text:String):String = {
318 if(retStr.startsWith("|")){
319 retStr = retStr.tail.toString
321 if(retStr.endsWith("|")){
322 retStr = retStr.init.toString
327 def _getAlign(alignList:List[String],i:Int):String = {
328 if(i >= alignList.size){""}else{alignList(i)}
331 if(doc == ""){return ""}
333 log debug "***" + doc
334 val p = new Regex(regex, "before","caption","css","headSeq","head","separatorSeq","sep","bodySeq","body","b","following")
335 val m = p findFirstMatchIn(doc)
338 val bef = m.get.group("before")
339 val fol = m.get.group("following")
340 var head = m.get.group("headSeq")
341 val sep = m.get.group("separatorSeq")
342 val body = m.get.group("bodySeq")
344 if(Option(m.get.group("caption")) != None){
345 cap = s"""<caption>${m.get.group("caption")}</caption>"""
348 if(Option(m.get.group("css")) != None){
349 id = decideClassOrStyle(doc,m.get.group("css"))
352 if(Option(sep) != None){
353 val pSep = """((?:\|)?(:?-{3,}?:?)(?:\|)?)+?""".r
354 val mSep = pSep.findAllMatchIn(sep)
356 var tableList = List[List[String]]()
357 var tmpList = List[String]()
359 val align = mS.group(2)
360 if(align.startsWith(":") && align.endsWith(":")){
361 tmpList ::= """align="center" """
362 }else if(align.startsWith(":")){
363 tmpList ::= """align="left" """
364 }else if(align.endsWith(":")){
365 tmpList ::= """align="right" """
370 val alignList = tmpList.reverse
371 head = _normalize(head)
373 val heads = for((h,i) <- head.split("\\|").zipWithIndex)yield(s"""<th ${_getAlign(alignList,i)}>$h</th>\\,""")
374 val headList = heads.toList
375 if(headList.size != alignList.size){
376 log error "Table header is wrong.:" + headList
385 val pTBody = """((((\|)?(.*?)(\|)?)+?)\\,?)+?""".r
386 val mTBSeq = pTBody.findAllMatchIn(body)
387 var bodyList = List[String]()
391 for((mTBS,i) <- mTBSeq.zipWithIndex){
392 if(mTBS.group(2).contains("|")){
393 val row = _normalize(mTBS.group(2)).split("\\|")
394 val body = for((c,j) <- row.zipWithIndex)yield(s"""<td ${_getAlign(alignList,j)}>$c</td>\\,""")
395 bodyList ::= "<tr>\\\\," + body.mkString("") + "</tr>\\\\,"
397 folTmp += mTBS.group(2)
401 bodyList = bodyList.reverse
403 return surroundTableTAG(bef, regex, TAG) +
404 s"\\\\,<table $id>\\\\,$cap\\\\,<thead>\\\\," + s"<tr>${headList.mkString("")}</tr></thead>\\\\," +
405 s"<tbody>${bodyList.mkString("")}</tbody></table>\\\\," +
406 surroundTableTAG(folTmp + fol, regex, TAG)
414 private def autoMailLink(doc:String, regex:String, TAG:String):String = {
415 if(doc == ""){return ""}
417 val p = new Regex(regex, "before","inTAG","domain","following")
418 val m = p findFirstMatchIn(doc)
421 val bef = m.get.group("before")
422 val fol = m.get.group("following")
423 val mail = m.get.group("inTAG")
424 val domain = m.get.group("domain")
426 return autoMailLink(bef, regex, TAG) + "<address>" +
427 s"""<script type=\"text/javascript\">\\,document.write('<$TAG href=\\"mailto:$mail')\\,document.write(\"@\")\\,document.write(\"$domain\\">MailMe!</$TAG>\") </script>""" + "</address>" +
428 autoMailLink(fol, regex, TAG)
432 private def autoURLLink(doc:String, regex:String, TAG:String):String = {
433 if(doc == ""){return ""}
435 val p = new Regex(regex, "before","inTAG","following")
436 val m = p findFirstMatchIn(doc)
439 val bef = m.get.group("before")
440 val fol = m.get.group("following")
441 val url = m.get.group("inTAG")
443 return autoURLLink(bef, regex, TAG) + s"""<$TAG href=\"$url\">$url</$TAG> """ +
444 autoURLLink(fol, regex, TAG)
448 private def putImgTAG(doc:String, regex:String, TAG:String):String = {
449 if(doc == ""){return ""}
451 val p = new Regex(regex,"before","alt","url","title","resX","resY","css","following")
452 val m = p findFirstMatchIn(doc)
455 val bef = m.get.group("before")
456 val fol = m.get.group("following")
457 val alt = m.get.group("alt")
458 val url = m.get.group("url")
459 return putImgTAG(bef,regex,TAG) + s"""<$TAG src=\"$url\" alt=\"$alt\" ${getTitleName(m.get.group("title"))}""" +
460 s"""${getResolutionX(m.get.group("resX"))}${getResolutionY(m.get.group("resY"))}${decideClassOrStyle(doc,m.get.group("css"))}>""" +
461 putImgTAG(fol,regex,TAG)
466 private def getResolutionX(resX:String):String = Option(resX) match{
467 case None => return ""
468 case Some(x) => return s" width=$resX "
470 log error "unknown parameter has found." + resX
474 private def getResolutionY(resY:String):String = Option(resY) match{
475 case None => return ""
476 case Some(y) => return s" height=$resY "
478 log error "unknown parameter has found." + resY
481 private def searchCSSClassName(doc:String,cssClass:String):Boolean = {
482 val p = """(?i)<link.*?type=\"text\/css\".*?href=\"(.*?)\".*?>""".r
483 val m = p findFirstMatchIn(doc)
486 val fileName = m.get.group(1)
487 val CSSHandler = FileIO
488 val CSS = CSSHandler openCSSFile(fileName) mkString("")
490 for(line <- CSS.split("""\/\*.*?\*\/""")){
491 log debug "***" + line
492 if(line.contains(cssClass + " ")){return true}
498 private def surroundByCodeTAG(doc:String, regex:String, TAG:String):String = {
499 def _surroundByCodeTAG(doc:String,regex:String,innerSign:String,TAG:String):String = {
500 if(doc.contains(innerSign)){
502 val m = p.findFirstMatchIn(doc)
505 val bef = m.get.group(1)
506 var fol = m.get.group(2)
508 log debug "=>" + fol.head + " " + fol
510 if((fol.head.toString == sign)&&(sign != "``")){
516 log debug "**>" + follow
517 log debug "==>" + sign
518 val p2 = s"""^([^(?:\\,)]+?)$sign(.*)$$""".r
519 val m2 = p2.findFirstMatchIn(follow)
522 log debug ">>>>" + m2.get.group(1)
523 return _surroundByCodeTAG(bef, regex, "`", TAG) + s"<$TAG>" + ltgtExpand(m2.get.group(1)) + s"</$TAG>" +
524 _surroundByCodeTAG(m2.get.group(2), regex, "`", TAG)
526 if(fol.startsWith("```")){
527 return _surroundByCodeTAG(bef, regex, "`", TAG) + s"<$TAG></$TAG>" +
528 _surroundByCodeTAG(follow.drop(2), regex, "`", TAG)
529 }else if(fol.startsWith("`")){
530 return _surroundByCodeTAG(bef, regex, "`", TAG) + s"<$TAG></$TAG>" +
531 _surroundByCodeTAG(follow.drop(0), regex, "`", TAG)
533 log warn s"$sign CodeBlock is wrong."
540 _surroundByCodeTAG(doc,regex,"`",TAG)
543 private def referenceExpander(doc:String, regex:String, TAG:String):String = {
544 val m = regex.r.findFirstMatchIn(doc)
545 def expandAttribute(value:String):String = value match{
547 case _ => return value
552 if(m.get.group(3) != ""){
555 key = m.get.group(2).toLowerCase()
558 if(urlDefMap.contains(key)){
559 val tup5 = urlDefMap(key)
561 if(m.get.group(4) == null){
562 css = expandAttribute(tup5._5)
564 css = decideClassOrStyle(doc,m.get.group(4))
568 return referenceExpander(m.get.group(1), regex, TAG) +
569 s"""<$TAG href=\"${tup5._1}\" ${expandAttribute(tup5._2)}$css>""" +
570 m.get.group(2) + s"""</$TAG>""" + referenceExpander(m.get.group(5), regex, TAG)
572 return referenceExpander(m.get.group(1), regex, TAG) +
573 s"""<$TAG src=\"${tup5._1}\" alt=\"${m.get.group(2)}\" ${expandAttribute(tup5._2)}${expandAttribute(tup5._3)}${expandAttribute(tup5._4)} $css>""" +
574 referenceExpander(m.get.group(5), regex, TAG)
576 log error "Unknown Expand TAG from Reference"
580 log warn "Link definition was not found : " + key
589 private def urlDefinitions(doc:String):String = {
590 def _urlDefinitions(text:String):String = {
593 if(text == ""){return text}
595 log debug "doc ==>" + text
596 val p = new Regex("""^(.*?)?(((\[([\w\d\.\_\+\-\:\/)]+?)\]:([\w\d\.\_\+\-\:\/]+?)(?:\s+\"(.+?)\")?(?:\s+(\d+%?x\d+%?))?(?:\s+\{(.+?)\})?)\s*\\,)+?)(?:\\,|\z)(.*)?$$""",
597 "before","seq","elem1","elem2","landMark","link","Title","Res","Css", "following")
598 val m = p findFirstMatchIn(text)
601 if(m.get.group("before") != None){bef = m.get.group("before")}
602 if(m.get.group("following") != None){fol = m.get.group("following")}
604 log debug "bef=>" + bef
605 log debug "seq=>" + m.get.group("seq")
606 log debug "fol=>" + fol
607 if(m.get.group("seq") != None){
608 val seq = m.get.group("seq")
610 """\[([\w\d\.\_\+\-\:\/]+?)\]:([\w\d\.\_\+\-\:\/]+)(\s+\"(.+?)\")?(?:\s+(\d+%?x\d+%?))?(?:\s+\{(.+?)\})?(?:\s*\\,)?""".r.findAllMatchIn(seq)
612 val link = e.group(2)
613 log debug ">>" + link
614 val landMark = e.group(1)
615 log debug ">>>" + landMark
617 if(e.group(4) != null){title = s"""title=\"${e.group(4)}\" """}
620 if(Option(e.group(5)) != None){
621 val matResolution = """(\d+%?)x(\d+%?)""".r.findFirstMatchIn(e.group(5))
622 resX = getResolutionX(matResolution.get.group(1))
623 resY = getResolutionY(matResolution.get.group(2))
626 if(e.group(6) != null){
627 css = decideClassOrStyle(text,e.group(6))
629 urlDefMap += (landMark.toLowerCase->(link,title,resX,resY,css))
632 _urlDefinitions(bef) + _urlDefinitions(fol)
634 log debug "m was None!"
641 private def decideClassOrStyle(doc:String,className:String):String = {
642 if(className == "" || className == null){
646 if(!searchCSSClassName(doc,className)){
647 if(className.contains(":")){
648 return "style=\"" + className + "\""
649 }else if(className.startsWith("#")){
650 return "id=\"" + className + "\""
652 log warn s"$className not found..."
653 return "class=\"" + className +"\""
655 return "class=\"" + className +"\""
659 private def getTitleName(title:String):String = {
660 if(title == ""| title == null){
664 return s"""title=\"$title\" """
667 private def surroundaHrefTAG(doc:String,regex:String,TAG:String):String = {
668 val p = new Regex(regex,"before","inTag","link","title","css","following")
669 val m = p findFirstMatchIn(doc)
674 if(m.get.group("before") != None){
675 bef = m.get.group("before")
677 if(m.get.group("following") != None){fol = m.get.group("following")}
679 val link = m.get.group("link")
680 val label = m.get.group("inTag")
681 return surroundaHrefTAG(bef, regex, TAG) +
682 s"""<$TAG href=\"$link\" ${getTitleName(m.get.group("title"))}""" +
683 s"""${decideClassOrStyle(doc,m.get.group("css"))}>$label</$TAG>""" +
684 surroundaHrefTAG(fol,regex,TAG)
689 private def putHrTAG(doc:String, regex:String, TAG:String):String = {
690 val p = new Regex(regex,"before","line","following")
691 val m = p findFirstMatchIn(doc)
698 if(m.get.group("before") != None){
699 bef = m.get.group("before")
701 if(m.get.group("following") != None){fol = m.get.group("following")}
703 return putHrTAG(bef,regex,TAG) + s"<$TAG />" + putHrTAG(fol,regex,TAG)
708 private def surroundByPreCodeTAG(doc:String, regex:String, TAG:String):String = {
709 val p = new Regex(regex, "before","seq","inTAG","midInTag","following")
710 val m = p findFirstMatchIn(doc)
711 log debug "[" + doc + "]"
717 if(m.get.group("before") != None){
718 bef = m.get.group("before")
720 if(m.get.group("following") != None){fol = m.get.group("following")}
722 log debug "^^^" + m.get.group("seq")
723 val mat = """((?:\x20{4,}|\t+)(.*?\\,))+?""".r.findAllMatchIn(m.get.group("seq"))
725 contentStr += elem.group(2)
726 log debug "***" + contentStr
730 return surroundByPreCodeTAG(bef,regex,TAG) +
732 ltgtExpand(contentStr) +
733 s"</$TAG></pre>\\," +
734 surroundByPreCodeTAG(fol, regex, TAG)
740 private def ltgtExpand(doc:String):String = {
741 return doc.replaceAll("&","&").replaceAll("<",">").replaceAll(">",">")
744 private def surroundByBlockquoteTAG(doc:String, regex:String, TAG:String):String = {
745 val p = new Regex(regex, "before","inTAG","midInTag","following")
746 val m = p findFirstMatchIn(doc)
747 log debug "[" + doc + "]"
753 var mid = m.get.group("inTAG")
754 log debug "***-->" + mid
756 if(m.get.group("before") != None){
757 bef = m.get.group("before")
758 if(bef.startsWith(">")){
763 if(m.get.group("following") != None){fol = m.get.group("following")}else{fol = ""}
768 val mat = """(.+?\\,)+?""".r.findAllMatchIn(mid)
770 var inCurrentBQ = true
772 if(!mt.group(1).startsWith(">")||mt.group(1) == "\\\\,"){
776 val m = """.*?<br />\\,$$""".r.findFirstMatchIn(mt.group(1))
778 if(m == None){break = "<br />\\\\,"}
779 contentStr += mt.group(1).tail.replaceAll("\\\\,", break)
781 log debug "(" + mt.group(1) + ")"
782 following += mt.group(1)
789 log debug "bef=" + bef + " mid=" + contentStr + " fol=" + following
790 log debug "-->" + contentStr
791 return surroundByBlockquoteTAG(bef, regex, TAG) +
792 s"<$TAG>\\," + surroundByBlockquoteTAG(contentStr, regex, TAG) + s"</$TAG>\\," +
793 surroundByBlockquoteTAG(following, regex, TAG)
798 private def surroundByListTAG(doc:String, regex:String, TAG:String):String = {
799 val p = new Regex(regex, "before","elements","element","following")
800 val m = p findFirstMatchIn(doc)
806 if(m.get.group("before") != None){bef = m.get.group("before")}else{bef = ""}
807 if(m.get.group("following") != None){fol = m.get.group("following")}else{fol = ""}
808 s = m.get.group("elements")
817 var styles:((Int) => String) = null
822 styles = (index:Int) => {
823 (index/indentWidth)%3 match{
830 sign = "[\\*|\\+|\\-]"
833 styles = (index:Int) => {
834 (index/indentWidth)%4 match{
836 case 2 => "decimal-leading-zero"
837 case 3 => "upper-latin"
838 case 0 => "lower-latin"
846 var docList = List[String]()
847 for(elem <- s"""(\\x20+?$sign\\x20.+?\\\\,)+?""".r.findAllMatchIn(s)){
848 docList = elem.group(1)::docList
853 def _surroundByListTAG(doc:List[String],TAG:String,indent:Int):TreeNode[String] = {
854 var tree = new TreeNode[String]("")
855 if(doc.isEmpty){return tree}
857 log debug "====>" + doc
858 tree.add(new TreeNode("<" + sp + s""" style=\"list-style-type:${styles(indent)}\">"""))
860 var list = List.empty[Tuple3[String,Int,String]]
862 val m = s"""((\\x20+?)$sign\\x20(.+?)\\\\,)""".r.findFirstMatchIn(elem)
863 list = (m.get.group(1),m.get.group(2).size,m.get.group(3))::list
866 var restStr = List[String]()
867 if(list.isEmpty){return new TreeNode("")
868 }else{for(e <- list.reverse.tail){restStr = e._1::restStr}}
870 restStr = restStr.reverse
871 for(elem <- list.reverse){
873 tree.add(new TreeNode("<" + sp + s""" style=\"list-style-type:${styles(elem._2)}\">"""))
874 }else if(elem._2 < i){
875 tree.add(new TreeNode[String](s"</$sp>"*((i - elem._2)/indentWidth)))
877 tree.add(new TreeNode[String](s"<$TAG>" + elem._3 + s"</$TAG>\\,"))
881 restStr = List[String]("")
883 restStr = restStr.tail
887 tree.add(new TreeNode(s"</$sp>"*((i - indent)/indentWidth + 1)))
891 log debug "->" + docList
892 val r1 = s"""(\\x20*)${sign}.*?\\\\,""".r
893 val wS1 = r1.findFirstMatchIn(s)
895 val r2 = s"""(\\x20*)${sign}.*(\\\\,<br />.*?</blockquote>\\\\,)""".r
896 val wS2 = r2.findFirstMatchIn(s)
898 var wS:Option[Regex.Match] = null
899 if(wS2 != None){wS = wS2}else if(wS1 != None){wS = wS1}
901 for(e <- _surroundByListTAG(docList.reverse,"li",wS.get.group(1).size)){
904 if(wS == wS2){str += wS.get.group(2)}
906 log debug "!---->" + str
907 surroundByListTAG(bef,regex,TAG) + str + surroundByListTAG(fol,regex,TAG)
911 private def surroundByHeadTAGUnderlineStyle(doc:String, regex:String, TAG:String):String = {
912 if(doc == ""){return doc}
914 log debug "-->" + doc
915 val p = new Regex(regex, "before","inTAG","id","style","following")
916 val m = p findFirstMatchIn(doc)
924 if(m.get.group("before") != None){bef = m.get.group("before")}else{bef = ""}
925 if(m.get.group("following") != None){fol = m.get.group("following")}else{fol = ""}
926 contentStr = m.get.group("inTAG")
928 if(m.get.group("style").contains("-")){
934 if(Option(m.get.group("id")) != None){
935 id = s"""id="${m.get.group("id")}" """
938 return surroundByHeadTAGUnderlineStyle(bef, regex, TAG) +
939 s"<$headSign $id>$contentStr</$headSign>\\," + surroundByHeadTAGUnderlineStyle(fol, regex, TAG)
944 private def surroundByHeadTAG(doc:String, regex:String, TAG:String):String = {
945 if(doc == ""){return doc}
947 log debug "--> " + doc
948 val p = new Regex(regex, "before","startHead","inTAG","endHead","id","following")
949 val m = p findFirstMatchIn(doc)
957 if(m.get.group("before") != None){bef = m.get.group("before")}else{bef = ""}
958 if(m.get.group("following") != None){fol = m.get.group("following")}else{fol = ""}
959 contentStr = m.get.group("inTAG")
961 val headSize = m.get.group("startHead").size
962 val endHead = m.get.group("endHead")
963 log debug "-->" + endHead
964 val headTAG = TAG + headSize
965 if(Option(m.get.group("id")) != None){val hashCode = m.get.group("id");id = s"""id="$hashCode" """}
966 if(Option(endHead) != None){
967 val m2 = """^\x20(#+?)$$""".r("wantSize").findFirstMatchIn(endHead)
969 val size = m2.get.group("wantSize")
970 if(size.size != headSize){
971 contentStr += " " + size
972 log warn "Curious header expression was found. " +
973 s" BlackQuill represents this <$headTAG>$contentStr</$headTAG> ."
978 return surroundByHeadTAG(bef,regex,TAG) +
979 s"<${headTAG} $id>$contentStr</${headTAG}>\\," +
980 surroundByHeadTAG(fol,regex,TAG)
985 private def surroundByGeneralTAG(doc:String, regex:String, TAG:String):String = {
986 if(doc == ""||Option(doc) == None){return ""}
988 val p = new Regex(regex,"before","inTAG","following")
989 val m = p findFirstMatchIn(doc)
993 if(m.get.group("before") != None){bef = m.get.group("before")}else{bef = ""}
994 if(m.get.group("following") != None){fol = m.get.group("following")}else{fol = ""}
995 return surroundByGeneralTAG(bef,regex,TAG) +
996 s"<${TAG}>" + m.get.group("inTAG") + s"</${TAG}>" +
997 surroundByGeneralTAG(fol,regex,TAG)
1002 private def constructHEADER(doc:String):String = {
1003 val headTAG = "head"
1004 val titleTAG = "title"
1005 var title = "NO TITLE"
1007 val p = new Regex("""(?i)#{1,6}\x20(.+?)\\,""")
1008 val m = p findFirstMatchIn(doc)
1009 if(m != None){title = m.get.group(1)}
1011 s"<${headTAG}>\n<${titleTAG}>${title}</${titleTAG}>\n</${headTAG}>"
1014 def preProcessors(doc:String) :String = {
1015 var text = urlDefinitions(doc)
1016 text = gatheringFootnotesDefinition(text)
1020 def toHTML(markdown:String):String = {
1021 val docType = "<!DOCTYPE html>"
1022 val bodyTAG = "body"
1023 val htmlTAG = "html"
1024 var md = preProcessors(markdown + "\\,")
1026 for(k <- Syntax keys){
1027 md = Syntax(k)._2(md, k, Syntax(k)._1)
1030 md = backslashEscape(md)
1031 md = paragraphize(md)
1033 log info footnoteMap
1034 md += insertFootnoteDefinitions(footnoteMap)
1035 val header = constructHEADER(markdown)
1036 s"${docType}\n${header}\n<${htmlTAG}>\n<${bodyTAG}>\n${md.replaceAll("\\\\,","\n")}\n</${bodyTAG}>\n</${htmlTAG}>"
1039 private def paragraphize(doc:String):String = {
1040 val delimiter = """\,"""
1041 def f(text:String):String = {
1044 val BlockElements = new HTMLMap().BLOCKTags
1046 var isOneLineBlock = false
1050 for(l <- doc.split("\\" + delimiter)){
1051 isOneLineBlock = false
1054 for(e <- BlockElements){
1056 if(l.contains("<" + e) && l.contains("</" + e + ">")){
1057 isOneLineBlock = true;break;
1058 }else if(l.contains("<" + e)){
1060 }else if(l.contains("</" + e + ">")){
1061 isBlock -= 1;isOneLineBlock = true;break;
1065 log debug ">>>>" + l + "::" + isBlock + "|" + isOneLineBlock
1066 if(isBlock > 0 | isOneLineBlock){
1067 text += l + delimiter
1069 if(l != "" && isBlock <= 0){
1070 text += "<p>" + l + "</p>" + delimiter
1076 //var text = "<p>" + doc.replaceAll("\\\\,\\\\,","</p>\\\\,\\\\,<p>") + "</p>"
1077 //text.replaceAll("<p></p>","")
1080 private def backslashEscape(doc:String):String = {
1081 val escapeCharSet = Set("\\","`","*","_","{","}","[","]","(",")","#","+","-","!",":","|")
1084 if(bef.size > 2 && escapeCharSet.contains(e.toString) && bef.reverse.head.toString == "\\"){