OSDN Git Service

blockquote has implemented
[blackquill/BlackQuill.git] / src / main / scala / org / blackquill / engine / BQParser.scala
1 package org.blackquill.engine
2
3 // BlackQuill Copyright (C) 2013 set.minami<set.minami@gmail.com>
4 // License MIT see also LISENCE.txt
5 // BQParser.
6
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
14 import scala.xml._
15
16 import org.blackquill.engine._
17
18 class BQParser {
19         private val log:Log = LogFactory.getLog(classOf[BQParser])
20
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 _)
30         )
31         
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)
36             if(m != null){
37                 for(e <- m){
38                         e.group(3).replaceAll("\\,", "<br />\\,")
39                         log info e.group(3)
40                 }
41             }
42           }
43           text
44         }
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 + "]"
49                 
50           var bef = ""
51           var fol = ""
52           var contentStr = ""
53           if(m != None){
54             var mid = m.get.group("inTAG")
55             log info "***-->" + mid
56
57             if(m.get.group("before") != None){
58               bef = m.get.group("before")
59               if(bef.startsWith(">")){
60                 mid = bef + mid
61                 bef = ""
62               }
63             }else{bef = ""}
64                 if(m.get.group("following") != None){fol = m.get.group("following")}else{fol = ""}
65                 
66                 log info "=>" + mid
67                 var following = ""
68                 if(mid != null){
69                   val mat = """(.+?\\,)+?""".r.findAllMatchIn(mid)
70                   
71                   var inCurrentBQ = true                  
72                   for(mt <- mat){
73                     if(!mt.group(1).startsWith(">")||mt.group(1) == "\\\\,"){
74                         inCurrentBQ = false
75                     }
76                     if(inCurrentBQ){
77                       val m = """.*?<br />\\,$$""".r.findFirstMatchIn(mt.group(1))
78                       var break = "\\\\,"
79                       if(m == None){break = "<br />\\\\,"}
80                       contentStr += mt.group(1).tail.replaceAll("\\\\,", break)
81                     }else{
82                       log info "(" + mt.group(1) + ")"
83                       following += mt.group(1)
84                     }               
85                     log info "^^^" + mt
86                   }
87                 }
88                 
89                 following += fol
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)
95           }
96           doc
97         }
98
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)
102                 var s = ""
103                 var bef = ""
104                 var fol = ""
105
106                 if(m != None){
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")
110                 }else{
111                   return doc
112                 }
113
114                 
115                 var sign = ""
116                 var sp = ""
117                 val indentWidth = 4
118                 var styles:((Int) => String) = null
119                 
120                 TAG match{
121               case "ul"=>
122                 sp = TAG
123                 styles = (index:Int) => {
124                           (index/indentWidth)%3 match{
125                                 case 1 => "disc"
126                                 case 2 => "circle"
127                                 case 0 => "square"
128                                 case _ => "---"
129                           }
130                 }
131                 sign = "[\\*|\\+|\\-]"
132               case "ol"=>
133                 sp = TAG
134                 styles = (index:Int) => {
135                           (index/indentWidth)%4 match{
136                             case 1 => "decimal"
137                             case 2 => "decimal-leading-zero"
138                             case 3 => "upper-latin"
139                             case 0 => "lower-latin"                         
140                             case _ => "---"
141                           }
142                 }
143                 sign = "\\d+?\\."
144             }
145                 
146                 log debug ":::" + s
147                 var docList = List[String]()
148                 for(elem <- s"""(\\s+?$sign\\s.+?\\\\,)+?""".r.findAllMatchIn(s)){                               
149                         docList = elem.group(1)::docList
150                 }
151
152
153                 
154                 def _surroundByListTAG(doc:List[String],TAG:String,indent:Int):TreeNode[String] = {     
155                         var tree = new TreeNode[String]("")
156                     if(doc.isEmpty){return tree}                                                
157                         
158                         log debug "====>" + doc
159                         tree.add(new TreeNode("<" + sp + s""" style=\"list-style-type:${styles(indent)}\">"""))
160                         var i = indent
161                         var list = List.empty[Tuple3[String,Int,String]]
162                         for(elem <- doc){
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
165                         }
166
167                         var restStr = List[String]()
168                         if(list.isEmpty){return new TreeNode("")
169                         }else{for(e <- list.reverse.tail){restStr = e._1::restStr}}
170                         
171                         restStr = restStr.reverse
172                         for(elem <- list.reverse){                      
173                                 if(elem._2 > i){                           
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)))
177                                 }
178                                 tree.add(new TreeNode[String](s"<$TAG>" + elem._3 + s"</$TAG>\\,"))     
179                                 
180                                 
181                                 if(restStr.isEmpty){
182                                         restStr = List[String]("")                                
183                                 }else{
184                                         restStr = restStr.tail
185                                 }
186                                 i = elem._2
187                         }
188                 tree.add(new TreeNode(s"</$sp>"*((i - indent)/indentWidth + 1)))
189                 return tree
190           }
191                 
192                 log debug "->" + docList
193                 val r1 = s"""(\\s*)${sign}.*?\\\\,""".r
194                 val wS1 = r1.findFirstMatchIn(s)
195                 var str = ""
196                 val r2 = s"""(\\s*)${sign}.*(\\\\,<br />.*?</blockquote>\\\\,)""".r
197                 val wS2 = r2.findFirstMatchIn(s)
198                 
199                 var wS:Option[Regex.Match] = null
200                 if(wS2 != None){wS = wS2}else if(wS1 != None){wS = wS1}
201                 if(wS != None){
202                         for(e <- _surroundByListTAG(docList.reverse,"li",wS.get.group(1).size)){
203                           str += e.toString()
204                         }
205                   if(wS == wS2){str += wS.get.group(2)}
206                   
207                   log debug "!---->" + str
208                   surroundByListTAG(bef,regex,TAG) + str + surroundByListTAG(fol,regex,TAG)
209                 }else{doc}
210         }
211         
212         private def surroundByHeadTAGUnderlineStyle(doc:String, regex:String, TAG:String):String = {
213           if(doc == ""){return doc}
214           
215           log debug "-->" + doc
216           val p = new Regex(regex, "before","inTAG","style","following")
217           val m = p findFirstMatchIn(doc)
218           var bef = ""
219           var fol = ""
220           var contentStr = ""
221           var headSign = TAG
222           if(m != None){
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")
226           
227             if(m.get.group("style").contains("-")){
228               headSign += "2"
229             }else{
230               headSign += "1"
231             }
232           
233             return surroundByHeadTAGUnderlineStyle(bef, regex, TAG) +
234                           s"<$headSign>$contentStr</$headSign>\\," + surroundByHeadTAGUnderlineStyle(fol, regex, TAG)
235           }
236           doc
237         }
238         
239         private def surroundByHeadTAG(doc:String, regex:String, TAG:String):String = {
240                 if(doc == ""){return doc}
241                 
242                 log debug "--> " + doc
243                 val p = new Regex(regex, "before","startHead","inTAG","endHead","following")
244                 val m = p findFirstMatchIn(doc)
245                 var contentStr = ""
246                 if(m != None){
247               var bef = ""
248               var fol = ""
249               
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")
253               
254                   val headSize = m.get.group("startHead").size
255                   val endHead = m.get.group("endHead")
256                   log debug "-->" + endHead
257                   val headTAG = TAG + headSize
258                   if(endHead != null){
259                     val m2 = """^\s(#+?)$$""".r("wantSize").findFirstMatchIn(endHead)
260                     if(m2 != None){
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> ."
266                       }
267                     }
268                   }
269                   
270                   return surroundByHeadTAG(bef,regex,TAG) +
271                                   s"<${headTAG}>$contentStr</${headTAG}>\\," +
272                                   surroundByHeadTAG(fol,regex,TAG)
273                 }
274           doc
275         }
276         
277         private def surroundByGeneralTAG(doc:String, regex:String, TAG:String):String = {
278           if(doc == ""){return doc}
279           log debug doc
280           val p = new Regex(regex,"before","inTAG","following")
281           val m = p findFirstMatchIn(doc)
282           if(m != None){
283               var bef = ""
284               var fol = ""
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)
290           }
291           doc
292         }
293         
294         private def constructHEADER(doc:String):String = {
295           val headTAG = "head"
296           val titleTAG = "title"
297           var title = "NO TITLE"
298           
299           val p = new Regex("""(?i)#{1,6}\s(.+?)\\,""")
300           val m = p findFirstMatchIn(doc)
301           if(m != None){title = m.get.group(1)}
302           
303           s"<${headTAG}>\n<${titleTAG}>${title}</${titleTAG}>\n</${headTAG}>"
304         }
305         
306         def toHTML(markdown:String):String = {
307                 val docType = "<!DOCTYPE html>"
308                 val bodyTAG = "body"
309                 val htmlTAG = "html"
310                 var md = markdown
311                 for(k <- Syntax keys){
312                  md = Syntax(k)._2(md, k, Syntax(k)._1)
313                 }
314                 val header = constructHEADER(markdown)
315                 s"${docType}\n${header}\n<${htmlTAG}>\n<${bodyTAG}>\n${layoutControl(md, specialLayout).replaceAll("\\\\,","\n")}\n</${bodyTAG}>\n</${htmlTAG}>"
316         }
317         
318         
319 }