1
2
3 require 'logger'
4 require 'strscan'
5 require 'uri'
6
7 class PukipaParseError < StandardError;end
8
9 class Pukipa
10 def initialize(plain)
11 @plain = plain
12 @log = Logger::new( $deferr )
13 @log.level = $DEBUG ?
14 Logger::DEBUG :
15 ($VERBOSE ? Logger::INFO : Logger::WARN)
16 @h_start_level = 2
17 @pagelist = nil
18 end
19
20
21
22 def pagelist(pagelist,base_uri = '/',suffix = '/')
23 @base_uri = base_uri
24 @pagelist_suffix = suffix
25 if pagelist.size > 0
26
27 pagelist.reject!{|pn| pn.size <= 3 }
28 pagelist.map!{|pn| Regexp.escape(pn)}
29 @pagelist = Regexp.new('(?!<a.*?>.*?)((?:' + pagelist.join(')|(?:') + '))(?!.*?</a>)',Regexp::IGNORECASE )
30 end
31 end
32
33
34 def to_html
35 plain = @plain.gsub(/\r?\n/,"\n").chomp + "\n"
36 result = []
37 block_regex = /^([:\-+> ]).*\n(?:(?:\1.*\n)+)?/
38 @scanner = StringScanner.new( plain )
39 while !@scanner.eos?
40 if @scanner.scan(/^\n/)
41
42 elsif @scanner.scan(/^----.*?\n/)
43 result << '<hr />'
44 elsif @scanner.scan(block_regex)
45 result << block_parse(@scanner.matched)
46 elsif @scanner.scan(/^\*.*?\n/)
47 result << h_parse(@scanner.matched)
48 elsif @scanner.scan(/^([^*:\-+> \n].+?\n){1,}/)
49 result << block_parse(@scanner.matched)
50 elsif @scanner.scan(/(.*)/)
51 result << block_parse(@scanner.matched)
52 else
53
54 raise PukipaParseError.new
55 end
56 end
57 result.join("\n")
58 end
59
60 protected
61
62 def h_parse(str)
63 str.chomp!
64
65 h_regexs = {
66 '*' => 'h' + @h_start_level.to_s,
67 '**' => 'h' + (@h_start_level + 1).to_s,
68 '***' => 'h' + (@h_start_level + 2).to_s,
69 '****' => 'h' + (@h_start_level + 3).to_s,
70 }
71 h_regexs.each do |tmp_regex,prefix|
72 regex = Regexp.new('^' + Regexp.escape(tmp_regex) + '([^*])')
73 if str =~ regex
74 str.gsub!(regex,"\\1").gsub!(/^\s+/,'')
75 str = "<%s>%s</%s>" % [prefix,str_parse(str),prefix]
76 break
77 end
78 end
79 @log.debug "inline:\n%s" % str
80 str
81 end
82
83
84 def block_parse(str)
85 str.chomp!
86
87 if str =~ /^ /
88 str = str.map{|s| s.gsub(/^ /,'')}.join
89
90 str = ['<pre><code>' + escapeHTML(str),'</code></pre>'].join "\n"
91 elsif str =~ /^>/
92 str = str.map{|s| s.gsub(/^>/,'')}.join
93 str = ['<blockquote><p>',str_parse(str),'</p></blockquote>'].join "\n"
94 elsif str =~ /^-/
95 str = list_parse(str,'ul')
96 elsif str =~ /^\+/
97 str = list_parse(str,'ol')
98 elsif str =~ /^:/
99 str = dl_parse(str)
100 else
101 str = ['<p>',str_parse(str),'</p>'].join "\n"
102 end
103 @log.debug "block:\n%s" % str
104 str
105 end
106
107
108 def list_parse(str,list)
109 if list == 'ul'
110 regex = /^-/
111 else
112 regex = /^\+/
113 end
114 str = str.map{|s| s.gsub(regex,'')}.join
115 result = []
116 result << "<#{list}>"
117 tmp = []
118 str.each_line do |s|
119 s.chomp!
120 if s =~ regex
121 tmp << s.gsub(regex,'')
122 else
123 if tmp.size > 0
124 result.pop
125 result << list_parse(tmp.join("\n"),list)
126 result << "</li>"
127 tmp.clear
128 end
129 result << "<li>#{str_parse(s)}"
130 result << "</li>"
131 end
132 end
133 if tmp.size > 0
134 result.pop
135 result << list_parse(tmp.join("\n"),list)
136 result << "</li>"
137 tmp.clear
138 end
139 result << "</#{list}>"
140 result.join "\n"
141 end
142
143
144 def dl_parse(str)
145 regex = /^:/
146 regex2 = /(.*?)\|(.*)/
147 str = str.map{|s| s.gsub(regex,'')}.join
148 result = []
149 result << "<dl>"
150 tmp = []
151 str.each_line do |s|
152 s.chomp!
153 m = regex2.match(s)
154 if m
155 result << "<dt>" + str_parse(m[1]) + "</dt>"
156 result << "<dd>" + str_parse(m[2]) + "</dd>"
157 else
158 result << "<dt>" + str_parse(s) + "</dt>"
159 end
160 end
161 result << "</dl>"
162 result.join "\n"
163 end
164
165
166 def str_parse(str)
167
168 uri_regex = Regexp.new('(?!\[\[.*?)(' + URI.regexp('http').source + ')(?!.*?\]\])',Regexp::EXTENDED)
169 str.gsub!(uri_regex) do |match|
170 uri = $1.dup
171 re = match
172 re = '[[%s:%s]]' % [uri,uri] if not uri =~ /\]\]$/
173 re
174 end
175
176 str = escapeHTML(str)
177
178
179 str.gsub!(/\[\[(.+?):\s*(https?:\/\/.+?)\s*\]\]/) do
180 name = $1.dup
181 uri = $2.dup
182 '<a class="outlink" href="%s">%s</a>' % [uri,name]
183 end
184
185
186 if @pagelist
187 str.gsub!(@pagelist) do
188 s = $1.dup
189 '<a class="pagelink" href="%s%s%s">%s</a>' % [@base_uri,escape(s),@pagelist_suffix,s]
190 end
191 end
192
193 str
194 end
195
196 def escapeHTML(string)
197 string.gsub(/&/n, '&').gsub(/\"/n, '"').gsub(/>/n, '>').gsub(/</n, '<')
198 end
199
200 def escape(string)
201 string.gsub(/([^ a-zA-Z0-9_.-]+)/n) do
202 '%' + $1.unpack('H2' * $1.size).join('%').upcase
203 end.tr(' ', '+')
204 end
205 end
206
207 if $0 == __FILE__
208 body =<<EOF
209 *Wiki文法
210
211 **見出し
212 *h2
213 **h3
214 ***h4
215 ****h5
216 *h2
217 **h3
218 ***h4
219 ****h5
220
221 **リスト
222 -list
223 -list2
224 --list2-1
225 --list2-2
226 -list
227 -list2
228 --list2-1
229 --list2-2
230
231 **番号付きリスト
232 +olist
233 +olist2
234 ++olist2-1
235 ++olist2-2
236 +olist
237 +olist2
238 ++olist2-1
239 ++olist2-2
240
241 **引用
242 >引用文
243 >example
244 >引用文
245 >example
246
247 **pre
248 頭にスペースを入れる
249 def takahashi
250 'takahashi'
251 end
252
253 **定義語
254 :apple|リンゴ
255 :orange|オランゲ
256 :apple|リンゴ
257 :orange|オランゲ
258
259 **リンク
260 [[example:http://example.com]]
261 [[example:http://example.com]]
262
263 ページ名には自動リンクされる。
264
265 例:)文法
266 EOF
267 pu = Pukipa.new(body)
268 pu.pagelist(['文法','自動リンク'])
269 puts pu.to_html
270 end