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.Stack
11 import scala.collection.mutable.ListMap
12 import scala.collection.SortedSet
13 import scala.util.matching.Regex
16 import org.blackquill.engine._
19 private val log:Log = LogFactory.getLog(classOf[BQParser])
21 private val Syntax = LinkedHashMap(
22 "^(.*?\\\\,)((>.*(?:\\\\,))+?)(.*?)$$" -> ("blockquote",surroundByBlockquoteTAG _),
23 "^(.*?)((\\s+\\d+?\\.\\s.+?\\\\,)+)(.*?)$$" -> ("ol",surroundByListTAG _),
24 "^(.*?)((\\s+(?:\\*|\\+|\\-)\\s.+?\\\\,)+)(.*?)$$" -> ("ul",surroundByListTAG _),
25 "^(.*?)\\*\\*(.+?)\\*\\*(.*?)$$" -> ("strong",surroundByGeneralTAG _),
26 "^(.*?)\\*(.+?)\\*(.*?)$$" -> ("em",surroundByGeneralTAG _),
27 "^(.*?)(#{1,6})\\s(.+?)(\\s#{1,6}?)??\\\\,(.*?)$$" -> ("h",surroundByHeadTAG _),
28 "^(.*\\\\,)(.*?)\\\\,(\\-+|=+)\\s*\\\\,(.*)$$" -> ("h",surroundByHeadTAGUnderlineStyle _)
29 //"^(.*?)(\\\\,.+?\\\\,)(.*?)$$" -> ("p",surroundByAbstructTAG _)
32 private val specialLayout:List[String] = List("blockquote")
33 private def layoutControl(text:String,tags:List[String]):String = {
34 for(l <- tags.iterator){
35 val m = s"""^(.*?)<$l>((.*?\\,)+?)</$l>(.*?)$$""".r.findAllMatchIn(text)
38 e.group(3).replaceAll("\\,", "<br />\\,")
45 private def surroundByBlockquoteTAG(doc:String, regex:String, TAG:String):String = {
46 val p = new Regex(regex, "before","inTAG","midInTag","following")
47 val m = p findFirstMatchIn(doc)
48 log info "[" + doc + "]"
54 var mid = m.get.group("inTAG")
55 log info "***-->" + mid
57 if(m.get.group("before") != None){
58 bef = m.get.group("before")
59 if(bef.startsWith(">")){
64 if(m.get.group("following") != None){fol = m.get.group("following")}else{fol = ""}
69 val mat = """(.+?\\,)+?""".r.findAllMatchIn(mid)
71 var inCurrentBQ = true
73 if(!mt.group(1).startsWith(">")||mt.group(1) == "\\\\,"){
77 val m = """.*?<br />\\,$$""".r.findFirstMatchIn(mt.group(1))
79 if(m == None){break = "<br />\\\\,"}
80 contentStr += mt.group(1).tail.replaceAll("\\\\,", break)
82 log info "(" + mt.group(1) + ")"
83 following += mt.group(1)
90 log info "bef=" + bef + " mid=" + contentStr + " fol=" + following
91 log info "-->" + contentStr
92 return surroundByBlockquoteTAG(bef, regex, TAG) +
93 s"<$TAG>\\," + surroundByBlockquoteTAG(contentStr, regex, TAG) + s"</$TAG>\\," +
94 surroundByBlockquoteTAG(following, regex, TAG)
99 private def surroundByListTAG(doc:String, regex:String, TAG:String):String = {
100 val p = new Regex(regex, "before","elements","element","following")
101 val m = p findFirstMatchIn(doc)
107 if(m.get.group("before") != None){bef = m.get.group("before")}else{bef = ""}
108 if(m.get.group("following") != None){fol = m.get.group("following")}else{fol = ""}
109 s = m.get.group("elements")
118 var styles:((Int) => String) = null
123 styles = (index:Int) => {
124 (index/indentWidth)%3 match{
131 sign = "[\\*|\\+|\\-]"
134 styles = (index:Int) => {
135 (index/indentWidth)%4 match{
137 case 2 => "decimal-leading-zero"
138 case 3 => "upper-latin"
139 case 0 => "lower-latin"
147 var docList = List[String]()
148 for(elem <- s"""(\\s+?$sign\\s.+?\\\\,)+?""".r.findAllMatchIn(s)){
149 docList = elem.group(1)::docList
154 def _surroundByListTAG(doc:List[String],TAG:String,indent:Int):TreeNode[String] = {
155 var tree = new TreeNode[String]("")
156 if(doc.isEmpty){return tree}
158 log debug "====>" + doc
159 tree.add(new TreeNode("<" + sp + s""" style=\"list-style-type:${styles(indent)}\">"""))
161 var list = List.empty[Tuple3[String,Int,String]]
163 val m = s"""((\\s+?)$sign\\s(.+?)\\\\,)""".r.findFirstMatchIn(elem)
164 list = (m.get.group(1),m.get.group(2).size,m.get.group(3))::list
167 var restStr = List[String]()
168 if(list.isEmpty){return new TreeNode("")
169 }else{for(e <- list.reverse.tail){restStr = e._1::restStr}}
171 restStr = restStr.reverse
172 for(elem <- list.reverse){
174 tree.add(new TreeNode("<" + sp + s""" style=\"list-style-type:${styles(elem._2)}\">"""))
175 }else if(elem._2 < i){
176 tree.add(new TreeNode[String](s"</$sp>"*((i - elem._2)/indentWidth)))
178 tree.add(new TreeNode[String](s"<$TAG>" + elem._3 + s"</$TAG>\\,"))
182 restStr = List[String]("")
184 restStr = restStr.tail
188 tree.add(new TreeNode(s"</$sp>"*((i - indent)/indentWidth + 1)))
192 log debug "->" + docList
193 val r1 = s"""(\\s*)${sign}.*?\\\\,""".r
194 val wS1 = r1.findFirstMatchIn(s)
196 val r2 = s"""(\\s*)${sign}.*(\\\\,<br />.*?</blockquote>\\\\,)""".r
197 val wS2 = r2.findFirstMatchIn(s)
199 var wS:Option[Regex.Match] = null
200 if(wS2 != None){wS = wS2}else if(wS1 != None){wS = wS1}
202 for(e <- _surroundByListTAG(docList.reverse,"li",wS.get.group(1).size)){
205 if(wS == wS2){str += wS.get.group(2)}
207 log debug "!---->" + str
208 surroundByListTAG(bef,regex,TAG) + str + surroundByListTAG(fol,regex,TAG)
212 private def surroundByHeadTAGUnderlineStyle(doc:String, regex:String, TAG:String):String = {
213 if(doc == ""){return doc}
215 log debug "-->" + doc
216 val p = new Regex(regex, "before","inTAG","style","following")
217 val m = p findFirstMatchIn(doc)
223 if(m.get.group("before") != None){bef = m.get.group("before")}else{bef = ""}
224 if(m.get.group("following") != None){fol = m.get.group("following")}else{fol = ""}
225 contentStr = m.get.group("inTAG")
227 if(m.get.group("style").contains("-")){
233 return surroundByHeadTAGUnderlineStyle(bef, regex, TAG) +
234 s"<$headSign>$contentStr</$headSign>\\," + surroundByHeadTAGUnderlineStyle(fol, regex, TAG)
239 private def surroundByHeadTAG(doc:String, regex:String, TAG:String):String = {
240 if(doc == ""){return doc}
242 log debug "--> " + doc
243 val p = new Regex(regex, "before","startHead","inTAG","endHead","following")
244 val m = p findFirstMatchIn(doc)
250 if(m.get.group("before") != None){bef = m.get.group("before")}else{bef = ""}
251 if(m.get.group("following") != None){fol = m.get.group("following")}else{fol = ""}
252 contentStr = m.get.group("inTAG")
254 val headSize = m.get.group("startHead").size
255 val endHead = m.get.group("endHead")
256 log debug "-->" + endHead
257 val headTAG = TAG + headSize
259 val m2 = """^\s(#+?)$$""".r("wantSize").findFirstMatchIn(endHead)
261 val size = m2.get.group("wantSize")
262 if(size.size != headSize){
263 contentStr += " " + size
264 log warn "Curious header expression was found. " +
265 s" BlackQuill represents this <$headTAG>$contentStr</$headTAG> ."
270 return surroundByHeadTAG(bef,regex,TAG) +
271 s"<${headTAG}>$contentStr</${headTAG}>\\," +
272 surroundByHeadTAG(fol,regex,TAG)
277 private def surroundByGeneralTAG(doc:String, regex:String, TAG:String):String = {
278 if(doc == ""){return doc}
280 val p = new Regex(regex,"before","inTAG","following")
281 val m = p findFirstMatchIn(doc)
285 if(m.get.group("before") != None){bef = m.get.group("before")}else{bef = ""}
286 if(m.get.group("following") != None){fol = m.get.group("following")}else{fol = ""}
287 return surroundByGeneralTAG(bef,regex,TAG) +
288 s"<${TAG}>" + m.get.group("inTAG") + s"</${TAG}>" +
289 surroundByGeneralTAG(fol,regex,TAG)
294 private def constructHEADER(doc:String):String = {
296 val titleTAG = "title"
297 var title = "NO TITLE"
299 val p = new Regex("""(?i)#{1,6}\s(.+?)\\,""")
300 val m = p findFirstMatchIn(doc)
301 if(m != None){title = m.get.group(1)}
303 s"<${headTAG}>\n<${titleTAG}>${title}</${titleTAG}>\n</${headTAG}>"
306 def toHTML(markdown:String):String = {
307 val docType = "<!DOCTYPE html>"
311 for(k <- Syntax keys){
312 md = Syntax(k)._2(md, k, Syntax(k)._1)
314 val header = constructHEADER(markdown)
315 s"${docType}\n${header}\n<${htmlTAG}>\n<${bodyTAG}>\n${layoutControl(md, specialLayout).replaceAll("\\\\,","\n")}\n</${bodyTAG}>\n</${htmlTAG}>"