org-parse 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.document +5 -0
- data/.gitignore +23 -0
- data/ChangeLog +4 -0
- data/LICENSE +20 -0
- data/README.rdoc +68 -0
- data/Rakefile +54 -0
- data/VERSION.yml +5 -0
- data/bin/org-parse +51 -0
- data/bin/org-test +74 -0
- data/doc/images/org-parse-struct_1ffae50f0c5eb867f9418df6800f40a5cc3d1751.png +0 -0
- data/doc/org-parse.html +203 -0
- data/doc/org-parse.org +71 -0
- data/doc/struct.dot +10 -0
- data/doc/struct.png +0 -0
- data/examples/body-only.html.erb +1 -0
- data/examples/dot.org-parse-rc +21 -0
- data/lib/org-parse/inline-parser.output +945 -0
- data/lib/org-parse/inline-parser.rb +219 -0
- data/lib/org-parse/inline-parser.ry +77 -0
- data/lib/org-parse/inline-parser.tab.rb +411 -0
- data/lib/org-parse/node.rb +329 -0
- data/lib/org-parse/struct-parser.output +1019 -0
- data/lib/org-parse/struct-parser.rb +78 -0
- data/lib/org-parse/struct-parser.ry +125 -0
- data/lib/org-parse/struct-parser.tab.rb +608 -0
- data/lib/org-parse/struct-scanner.rb +272 -0
- data/lib/org-parse/templates/single.html.erb +118 -0
- data/lib/org-parse/textile-visitor.rb +296 -0
- data/lib/org-parse/utils.rb +15 -0
- data/lib/org-parse/visitor.rb +542 -0
- data/lib/org-parse.rb +46 -0
- data/org-parse.gemspec +113 -0
- data/rakelib/racc.rake +16 -0
- data/test/data/blocks.org +67 -0
- data/test/data/emphasis.org +7 -0
- data/test/data/footnote.html +136 -0
- data/test/data/footnote.org +8 -0
- data/test/data/html-export.html +1062 -0
- data/test/data/html-export.org +342 -0
- data/test/data/images.html +179 -0
- data/test/data/images.org +30 -0
- data/test/data/index.org +242 -0
- data/test/data/lily20100228.jpg +0 -0
- data/test/data/lily20100228t.jpg +0 -0
- data/test/data/link.org +7 -0
- data/test/data/list_before_1st_headline.html +119 -0
- data/test/data/list_before_1st_headline.org +7 -0
- data/test/data/lists.html +284 -0
- data/test/data/lists.org +78 -0
- data/test/data/no-headline.org +6 -0
- data/test/data/one-headline.org +2 -0
- data/test/data/paragraph.org +13 -0
- data/test/data/quote.org +15 -0
- data/test/data/sections.html +173 -0
- data/test/data/sections.org +9 -0
- data/test/data/simple-list.org +6 -0
- data/test/data/skip_t.org +3 -0
- data/test/data/structure.org +53 -0
- data/test/data/table.org +14 -0
- data/test/data/test-list.org +12 -0
- data/test/data/text-bef-hl.org +5 -0
- data/test/data/text.org +6 -0
- data/test/data/title.html +88 -0
- data/test/data/title.org +6 -0
- data/test/data/verse.org +48 -0
- data/test/helper.rb +31 -0
- data/test/test_org-parse.rb +148 -0
- metadata +134 -0
@@ -0,0 +1,272 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require ::File.join(OrgParse::LIBPATH , 'org-parse', 'inline-parser.rb')
|
3
|
+
require ::File.join(OrgParse::LIBPATH , 'org-parse', 'utils.rb')
|
4
|
+
require 'yaml'
|
5
|
+
|
6
|
+
|
7
|
+
module OrgParse
|
8
|
+
|
9
|
+
# Org-modeの文字列を、構造レベルのトークンに分解する
|
10
|
+
#
|
11
|
+
# 処理は、行単位で行う
|
12
|
+
#
|
13
|
+
class StructScanner
|
14
|
+
include Utils
|
15
|
+
include InlineUtils
|
16
|
+
|
17
|
+
GENERATOR = "OrgParse #{VERSION} Powered by Ruby #{RUBY_VERSION}"
|
18
|
+
|
19
|
+
ListSymbols = {
|
20
|
+
:UL_START => :UL_END, :OL_START => :OL_END,
|
21
|
+
:DL_START => :DL_END,
|
22
|
+
}
|
23
|
+
|
24
|
+
# コンストラクタ
|
25
|
+
# [_src_] ソース文字列(または、文字列の配列)
|
26
|
+
# ソース文字列は、1行単位の配列として @srcに保存する
|
27
|
+
# [_title_] skip:t の場合にタイトルとして使われる
|
28
|
+
def initialize(src, title)
|
29
|
+
@src = (src.is_a? Array) ? src : src.to_a
|
30
|
+
@line_idx = 0
|
31
|
+
@outline_level = 0
|
32
|
+
@nest_stack = []
|
33
|
+
@token_que = []
|
34
|
+
@section_stack = []
|
35
|
+
@options = { :H => 3, :skip => false, :toc => true, :num => true,
|
36
|
+
:author => nil,
|
37
|
+
:url => nil,
|
38
|
+
:email => nil,
|
39
|
+
:creator => GENERATOR, :timestamp => true,
|
40
|
+
:title => nil, :text => [], :language => 'ja', :charset => 'utf-8',
|
41
|
+
:default_title => title, :style => '', :uv => true,
|
42
|
+
}
|
43
|
+
read_options
|
44
|
+
opt = YAML.dump @options
|
45
|
+
File.open('dot.rc2', "w") {|f|
|
46
|
+
f.write opt
|
47
|
+
}
|
48
|
+
# p @options
|
49
|
+
end
|
50
|
+
|
51
|
+
# http://orgmode.org/manual/Export-options.html#Export-options
|
52
|
+
def read_options
|
53
|
+
@src.reject! do |line|
|
54
|
+
m = true
|
55
|
+
case line
|
56
|
+
when /^\s*#\+OPTIONS:\s/i
|
57
|
+
case $'
|
58
|
+
when /H:([0-9]+)/
|
59
|
+
@options[:H] = $1.to_i
|
60
|
+
when /skip:(\w)/
|
61
|
+
@options[:skip] = $1 != 'nil'
|
62
|
+
when /num:(\w+)/
|
63
|
+
@options[:num] = $1 != 'nil'
|
64
|
+
when /toc:(\w+)/
|
65
|
+
@options[:toc] = $1 != 'nil'
|
66
|
+
when /author:(\w+)/
|
67
|
+
@options[:author] = $1 != 'nil'
|
68
|
+
when /creator:(\w+)/
|
69
|
+
@options[:creator] = $1 != 'nil'
|
70
|
+
when /timestamp:(\w+)/
|
71
|
+
@options[:timestamp] = $1 != 'nil'
|
72
|
+
when /uv:(\w+)/
|
73
|
+
@options[:uv] = $1 != 'nil'
|
74
|
+
else
|
75
|
+
m = false
|
76
|
+
end
|
77
|
+
when /^\s*#\+TITLE:\s+(.*)$/i
|
78
|
+
@options[:title] = $1
|
79
|
+
when /^\s*#\+TEXT:\s+(.*)$/i
|
80
|
+
@options[:text] += line_parse($1 + "\n")
|
81
|
+
when /^\s*#\+LANGUAGE:\s+(.*)/i
|
82
|
+
@options[:language] = $1
|
83
|
+
when /^\s*#\+AUTHOR:\s*(.*)/i
|
84
|
+
@options[:author] = $1
|
85
|
+
when /^\s*#\+EMAIL:\s*(.*)/i
|
86
|
+
@options[:email] = $1
|
87
|
+
when /^\s*#\+STYLE:\s*(.*)$/i
|
88
|
+
@options[:style] += $1
|
89
|
+
else
|
90
|
+
m = false
|
91
|
+
end
|
92
|
+
m
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def next_token
|
97
|
+
return [[false, false],[]] if @token_que.empty?
|
98
|
+
# p @token_que[0]
|
99
|
+
token = @token_que.shift
|
100
|
+
vars = []
|
101
|
+
|
102
|
+
while token[0] == :VARIABLELINE and (token[1][0] == "ATTR_HTML" or token[1][0] == "CAPTION")
|
103
|
+
if token[1][0] == "CAPTION"
|
104
|
+
vars << "CAPTION:" + token[1][1]
|
105
|
+
else
|
106
|
+
vars << token[1][1]
|
107
|
+
end
|
108
|
+
return [[false, false], []] if @token_que.empty?
|
109
|
+
token = @token_que.shift
|
110
|
+
end
|
111
|
+
[token, vars]
|
112
|
+
end
|
113
|
+
|
114
|
+
# リスト開始ラインのネストをチェックする
|
115
|
+
# @nest_stack が、空なら新規リストの開始
|
116
|
+
# @nest_stack.last のインデントよりも、インデントが深ければ、
|
117
|
+
# ネストしたリストの開始
|
118
|
+
# @nest_stack.last のインデントとインデントが等しければ
|
119
|
+
# 新規のリストアイテムの開始
|
120
|
+
# @nest_stack.last のインデントよりインデントが浅ければ、
|
121
|
+
# @nest_stack.last のリストの終了後、新規リストを開始
|
122
|
+
def check_nest(kind, indent, string, dt = '')
|
123
|
+
last = @nest_stack.last
|
124
|
+
if @nest_stack.empty?
|
125
|
+
# puts "0: line: #{line.chomp} indent: #{indent} last: #{last.inspect}"
|
126
|
+
@nest_stack << [kind, indent]
|
127
|
+
@token_que << [kind, string]
|
128
|
+
@token_que << [:LI_START, [kind, string, dt]]
|
129
|
+
elsif last[1] < indent
|
130
|
+
# puts "1: line: #{line.chomp} indent: #{indent} last: #{last.inspect}"
|
131
|
+
@nest_stack << [kind, indent]
|
132
|
+
# @token_que << [:LI_END, line]
|
133
|
+
@token_que << [kind, string]
|
134
|
+
@token_que << [:LI_START, [kind, string, dt]]
|
135
|
+
elsif last[1] > indent
|
136
|
+
# puts "2: line: #{line.chomp} indent: #{indent} last: #{last.inspect}"
|
137
|
+
nest = @nest_stack.pop
|
138
|
+
@token_que << [:LI_END, string]
|
139
|
+
@token_que << [ListSymbols[nest[0]], ListSymbols[nest[0]]]
|
140
|
+
check_nest kind, indent, string
|
141
|
+
else
|
142
|
+
# puts "3: line: #{line.chomp} indent: #{indent} last: #{last.inspect}"
|
143
|
+
@token_que << [:LI_END, string]
|
144
|
+
@token_que << [:LI_START, [kind, string, dt]]
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
# if in list then exit list.
|
149
|
+
def exit_nests(line)
|
150
|
+
indent = get_indent line
|
151
|
+
while @nest_stack.last and @nest_stack.last[1] >= indent
|
152
|
+
nest = @nest_stack.pop
|
153
|
+
@token_que << [:LI_END, line]
|
154
|
+
@token_que << [ListSymbols[nest[0]], line]
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def exit_section(level)
|
159
|
+
cnt = 0
|
160
|
+
while @section_stack.last and @section_stack.last >= level
|
161
|
+
@token_que << [:SEC_END, level]
|
162
|
+
@section_stack.pop
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def skip_to_1st_headline
|
167
|
+
@line_idx += 1 while @src[@line_idx] !~ /^\*\s+[^\s]/
|
168
|
+
end
|
169
|
+
|
170
|
+
# scan before 1st headline
|
171
|
+
def scan_before_1st_headline
|
172
|
+
title = nil
|
173
|
+
if @options[:title]
|
174
|
+
title =@options[:title]
|
175
|
+
skip_to_1st_headline if @options[:skip]
|
176
|
+
elsif @options[:skip]
|
177
|
+
skip_to_1st_headline
|
178
|
+
title = @options[:default_title]
|
179
|
+
else
|
180
|
+
@line_idx += 1 while @src[@line_idx] =~ /^\s*$/
|
181
|
+
title = @src[@line_idx].chomp.sub(/^\s*/, '')
|
182
|
+
if title =~ /^\*+\s*/
|
183
|
+
title = @options[:default_title]
|
184
|
+
else
|
185
|
+
@line_idx += 1
|
186
|
+
end
|
187
|
+
end
|
188
|
+
@options[:title] = line_parse title
|
189
|
+
end
|
190
|
+
|
191
|
+
# split to tokens from @src array and set to @token_que
|
192
|
+
def scan
|
193
|
+
@line_idx = 0
|
194
|
+
scan_before_1st_headline
|
195
|
+
example_flag = false
|
196
|
+
@token_que << [:DOCUMENT_START, @options]
|
197
|
+
while @line_idx < @src.size
|
198
|
+
line = @src[@line_idx]
|
199
|
+
if example_flag && line !~ /^\s*#\+end_(example|html|src)/i
|
200
|
+
@token_que << [:TEXTLINE, [line, get_indent(line)]]
|
201
|
+
@line_idx += 1
|
202
|
+
next
|
203
|
+
end
|
204
|
+
|
205
|
+
case line
|
206
|
+
when /^\s*$/ # WHITELINE
|
207
|
+
@token_que << [:WHITELINE, line]
|
208
|
+
when /^(\*+)(\s+)COMMENT(\s)/
|
209
|
+
@line_idx += 1
|
210
|
+
@line_idx += 1 while @src[@line_idx] and @src[@line_idx] !~ /^\*+\s/
|
211
|
+
@line_idx -= 1 if @src[@line_idx]
|
212
|
+
when /^(\*+)(\s+)/
|
213
|
+
rest = $'
|
214
|
+
level = $1
|
215
|
+
exit_nests('')
|
216
|
+
exit_section(level.size)
|
217
|
+
@section_stack << level.size
|
218
|
+
@token_que << [:HEADLINE, [level, rest]]
|
219
|
+
when /^(\s*)-\s(.+)\s::\s+/
|
220
|
+
# Definition LIST
|
221
|
+
rest = $'
|
222
|
+
check_nest :DL_START, get_indent($1), rest, $2
|
223
|
+
when /^(\s*)[-+*]\s/
|
224
|
+
# Unordered LIST
|
225
|
+
rest = $'
|
226
|
+
check_nest :UL_START, get_indent($1), rest
|
227
|
+
when /^(\s*)[0-9]+(\.|\))\s+/
|
228
|
+
# Ordered LIST
|
229
|
+
rest = $'
|
230
|
+
check_nest :OL_START, get_indent($1), rest
|
231
|
+
when /^\s*#\+HTML:/
|
232
|
+
# #+HTML
|
233
|
+
rest = $'
|
234
|
+
@token_que << [:QUOTE, $']
|
235
|
+
when /^\s*#\+([^ :]+):\s*(.+)$/
|
236
|
+
@token_que << [:VARIABLELINE, [$1.upcase, $2.chomp]]
|
237
|
+
when /^(\s*):\s(.*)$/
|
238
|
+
@token_que << [:EXAMPLE, [$2+"\n", get_indent($1)]]
|
239
|
+
when /^\s*#\+BEGIN_([A-Z0-9_]+)/i # BLOCK
|
240
|
+
block_name = $1.upcase
|
241
|
+
exit_nests line
|
242
|
+
@token_que << [:BLOCK_START, [block_name, line, get_indent(line)]]
|
243
|
+
example_flag = true if ['EXAMPLE', 'HTML', 'SRC'].include? block_name.upcase
|
244
|
+
when /^\s*#\+END_([A-Z0-9_]+)/i # BLOCK
|
245
|
+
block_name = $1
|
246
|
+
exit_nests line
|
247
|
+
@token_que << [:BLOCK_END, [block_name, line]]
|
248
|
+
example_flag = false
|
249
|
+
when /^\s*\|[-\|\+]*\s*$/ # table separator
|
250
|
+
# an org-mode table separator has the first non-whitespace
|
251
|
+
# character as a | (pipe), then consists of nothing else other
|
252
|
+
# than pipes, hyphens, and pluses.
|
253
|
+
exit_nests line
|
254
|
+
@token_que << [:TABLE_SEP, line]
|
255
|
+
when /^\s*\|/ # table_row
|
256
|
+
# the first non-whitespace character is a | (pipe).
|
257
|
+
exit_nests line
|
258
|
+
@token_que << [:TABLE_ROW, line]
|
259
|
+
when /^\s*\[fn:([^\]]+)\]\s+(.+)$/
|
260
|
+
exit_nests line
|
261
|
+
@token_que << [:FOOTNOTE, [$1, $2]]
|
262
|
+
else
|
263
|
+
exit_nests line
|
264
|
+
@token_que << [:TEXTLINE, [line, get_indent(line)]]
|
265
|
+
end
|
266
|
+
@line_idx += 1
|
267
|
+
end
|
268
|
+
exit_nests ''
|
269
|
+
exit_section(0)
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
2
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
3
|
+
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
4
|
+
<html xmlns="http://www.w3.org/1999/xhtml"
|
5
|
+
lang="<%= @language %>" xml:lang="<%= @language %>">
|
6
|
+
<head>
|
7
|
+
<title><%= @title %></title>
|
8
|
+
<meta http-equiv="Content-Type" content="text/html;charset=<%= @charset %>"/>
|
9
|
+
<meta name="generator" content="Org-parse"/>
|
10
|
+
<meta name="generated" content="<%= Time.now.to_s %>"/>
|
11
|
+
<% if @options[:author] %>
|
12
|
+
<meta name="author" content="<%= @options[:author] %>"/>
|
13
|
+
<% end %>
|
14
|
+
<meta name="description" content=""/>
|
15
|
+
<meta name="keywords" content=""/>
|
16
|
+
<style type="text/css">
|
17
|
+
<!--/*--><![CDATA[/*><!--*/
|
18
|
+
html { font-family: "MS Pゴシック",Osaka,sans-serif,serif,monospace,cursive,fantasy; font-size: 12pt; }
|
19
|
+
.title { text-align: center; }
|
20
|
+
.todo { color: red; }
|
21
|
+
.done { color: green; }
|
22
|
+
.tag { background-color: #add8e6; font-weight:normal }
|
23
|
+
.target { }
|
24
|
+
.timestamp { color: #bebebe; }
|
25
|
+
.timestamp-kwd { color: #5f9ea0; }
|
26
|
+
p.verse { margin-left: 3% }
|
27
|
+
pre {
|
28
|
+
border: 1pt solid #AEBDCC;
|
29
|
+
background-color: #F3F5F7;
|
30
|
+
padding: 5pt;
|
31
|
+
font-family: courier, monospace;
|
32
|
+
font-size: 90%;
|
33
|
+
overflow:auto;
|
34
|
+
}
|
35
|
+
blockquote {
|
36
|
+
border: 1pt solid #AEBDCC;
|
37
|
+
background-color: #F3F5F7;
|
38
|
+
padding: 5pt;
|
39
|
+
font-family: courier, monospace;
|
40
|
+
font-size: 90%;
|
41
|
+
/* overflow:auto; */
|
42
|
+
}
|
43
|
+
table { border-collapse: collapse; }
|
44
|
+
td, th { vertical-align: top; }
|
45
|
+
dt { font-weight: bold; }
|
46
|
+
div.figure { padding: 0.5em; }
|
47
|
+
div.figure p { text-align: center; }
|
48
|
+
.linenr { font-size:smaller }
|
49
|
+
.code-highlighted {background-color:#ffff00;}
|
50
|
+
.org-info-js_info-navigation { border-style:none; }
|
51
|
+
#org-info-js_console-label { font-size:10px; font-weight:bold;
|
52
|
+
white-space:nowrap; }
|
53
|
+
.org-info-js_search-highlight {background-color:#ffff00; color:#000000;
|
54
|
+
font-weight:bold; }
|
55
|
+
/*]]>*/-->
|
56
|
+
</style>
|
57
|
+
<%= @add_to_head %>
|
58
|
+
<script type="text/javascript">
|
59
|
+
<!--/*--><![CDATA[/*><!--*/
|
60
|
+
function CodeHighlightOn(elem, id)
|
61
|
+
{
|
62
|
+
var target = document.getElementById(id);
|
63
|
+
if(null != target) {
|
64
|
+
elem.cacheClassElem = elem.className;
|
65
|
+
elem.cacheClassTarget = target.className;
|
66
|
+
target.className = "code-highlighted";
|
67
|
+
elem.className = "code-highlighted";
|
68
|
+
}
|
69
|
+
}
|
70
|
+
function CodeHighlightOff(elem, id)
|
71
|
+
{
|
72
|
+
var target = document.getElementById(id);
|
73
|
+
if(elem.cacheClassElem)
|
74
|
+
elem.className = elem.cacheClassElem;
|
75
|
+
if(elem.cacheClassTarget)
|
76
|
+
target.className = elem.cacheClassTarget;
|
77
|
+
}
|
78
|
+
/*]]>*///-->
|
79
|
+
</script>
|
80
|
+
</head>
|
81
|
+
<body>
|
82
|
+
<div id="content">
|
83
|
+
|
84
|
+
<h1 class="title"><%= @title %></h1>
|
85
|
+
|
86
|
+
<%= @before_text %>
|
87
|
+
|
88
|
+
<%= table_of_contents %>
|
89
|
+
|
90
|
+
<%= @body %>
|
91
|
+
|
92
|
+
<% unless @footnotes.empty? %>
|
93
|
+
<div id="footnotes">
|
94
|
+
<h2 class="footnotes">Footnotes: </h2>
|
95
|
+
<div id="text-footnotes">
|
96
|
+
<%= footnotes %>
|
97
|
+
</div>
|
98
|
+
</div>
|
99
|
+
<% end %>
|
100
|
+
|
101
|
+
<div id="postamble">
|
102
|
+
<% if @options[:author] %>
|
103
|
+
<p class="author"> Author: <%= @options[:author] %>
|
104
|
+
<% if @options[:email] %>
|
105
|
+
<a href="mailto:<%= @options[:email] %>"><<%= @options[:email] %>></a>
|
106
|
+
<% end %>
|
107
|
+
</p>
|
108
|
+
<% end %>
|
109
|
+
<% if @options[:timestamp] %>
|
110
|
+
<p class="date"> Date: <%= Time.now %> </p>
|
111
|
+
<% end %>
|
112
|
+
<% if @options[:creator] %>
|
113
|
+
<p class="creator">HTML generated by <%= @options[:creator] %></p>
|
114
|
+
<% end %>
|
115
|
+
</div>
|
116
|
+
</div>
|
117
|
+
</body>
|
118
|
+
</html>
|
@@ -0,0 +1,296 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'rubygems'
|
3
|
+
require ::File.join(OrgParse::LIBPATH , 'org-parse', 'visitor.rb')
|
4
|
+
|
5
|
+
module OrgParse
|
6
|
+
|
7
|
+
# todo:
|
8
|
+
# \\- ­ \\-
|
9
|
+
# -- – --
|
10
|
+
# --- — ---
|
11
|
+
# ... … \ldots
|
12
|
+
|
13
|
+
# output HTML format
|
14
|
+
class TextileVisitor < OrgVisitor
|
15
|
+
include VisitorUtils
|
16
|
+
|
17
|
+
def initialize(root, cm_opts = {})
|
18
|
+
@body = @title = @add_to_head = ''
|
19
|
+
@root = root
|
20
|
+
@ul_stack = []
|
21
|
+
@list_level = 0
|
22
|
+
@body = ''
|
23
|
+
@options = cm_opts
|
24
|
+
super()
|
25
|
+
end
|
26
|
+
|
27
|
+
# build the textile
|
28
|
+
def build
|
29
|
+
@options = @root.options
|
30
|
+
@before_text = exec_list @options[:text]
|
31
|
+
title = exec_list @root.options[:title]
|
32
|
+
body = "h1. " + title + "\n\n" + @before_text
|
33
|
+
@footnotes = []
|
34
|
+
@footnote_idxs = []
|
35
|
+
start_flag = true
|
36
|
+
@root.children.each do |node|
|
37
|
+
if start_flag and node.kind != :SECTION
|
38
|
+
@before_text += execute(node)
|
39
|
+
else
|
40
|
+
start_flag = false
|
41
|
+
body += execute(node)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
body += footnotes
|
45
|
+
end
|
46
|
+
|
47
|
+
def headline(node)
|
48
|
+
level = node.level+1
|
49
|
+
%Q|\nh#{level}. #{exec_children(node).chomp}\n\n|
|
50
|
+
end
|
51
|
+
|
52
|
+
# paragraph
|
53
|
+
# if @p_tag_flag == false then we don't output <p></p> tags.
|
54
|
+
def textblock(node)
|
55
|
+
if node.verse? or node.example? or node.html? or node.src?
|
56
|
+
exec_children node
|
57
|
+
elsif @p_tag_flag
|
58
|
+
"p. #{exec_children(node).sub(/^\s*/,'').chomp}\n"
|
59
|
+
else
|
60
|
+
str = exec_children node
|
61
|
+
@p_tag_flag = true # next paragraph has <p>
|
62
|
+
str
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def textline(node)
|
67
|
+
str = exec_children(node)
|
68
|
+
str = h str unless node.verse? or node.example? or node.html? or node.src?
|
69
|
+
str
|
70
|
+
end
|
71
|
+
|
72
|
+
def example(node)
|
73
|
+
body = exec_children(node)
|
74
|
+
%Q|<pre>\n#{ body.chomp }\n</pre>\n|
|
75
|
+
end
|
76
|
+
|
77
|
+
def src(node)
|
78
|
+
if @options[:redmine]
|
79
|
+
text = exec_children(node)
|
80
|
+
syntax = node.syntax
|
81
|
+
%Q|<pre><code class="#{syntax}">\n#{ body.chomp }\n</code></pre>\n|
|
82
|
+
else
|
83
|
+
example node
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def blockquote(node)
|
88
|
+
body = exec_children(node)
|
89
|
+
body.sub(/\n/, '<br/>')
|
90
|
+
%Q|\nbq.#{body.sub(/^\s*/,'').chomp}\n\n|
|
91
|
+
end
|
92
|
+
|
93
|
+
def blocks(node)
|
94
|
+
case node.block_name
|
95
|
+
when 'VERSE'
|
96
|
+
example node
|
97
|
+
when 'EXAMPLE'
|
98
|
+
example node
|
99
|
+
when 'QUOTE'
|
100
|
+
blockquote node
|
101
|
+
when 'HTML'
|
102
|
+
textblock node
|
103
|
+
when 'COMMENT'
|
104
|
+
''
|
105
|
+
when 'SRC'
|
106
|
+
src node
|
107
|
+
else
|
108
|
+
puts "not implimented block=>[#{node.block_name}](#{node.inspect})"
|
109
|
+
exec_children(node)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def link(node)
|
114
|
+
desc = exec_children node
|
115
|
+
if desc.empty?
|
116
|
+
if node.uri =~ @image_file_reg
|
117
|
+
" !#{node.uri.sub(/^file:/, '')}! "
|
118
|
+
else
|
119
|
+
" #{node.uri.sub(/^file:/, '')} "
|
120
|
+
end
|
121
|
+
else
|
122
|
+
%Q| "#{desc}":#{node.uri.sub(/^file:/, '')} |
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def table(node)
|
127
|
+
ret = ''
|
128
|
+
ret += exec_list node.hrows
|
129
|
+
ret += exec_children node
|
130
|
+
end
|
131
|
+
|
132
|
+
def table_row(node)
|
133
|
+
ret = ''
|
134
|
+
s = node.is_head? ? '|_.' : '|'
|
135
|
+
node.children.each {|cols|
|
136
|
+
cols.each{|col|
|
137
|
+
ret += s + execute(col)
|
138
|
+
}
|
139
|
+
}
|
140
|
+
ret += "|\n"
|
141
|
+
end
|
142
|
+
|
143
|
+
def variable(node)
|
144
|
+
fvs = ['CAPTION', 'ATTR_HTML', 'TEXT']
|
145
|
+
if fvs.include? node.name
|
146
|
+
@flash_vars[node.name] = node.value
|
147
|
+
else
|
148
|
+
@options[node.name] = node.value
|
149
|
+
end
|
150
|
+
''
|
151
|
+
end
|
152
|
+
|
153
|
+
def clear_vars
|
154
|
+
@flash_vars.clear
|
155
|
+
end
|
156
|
+
|
157
|
+
def lists(node)
|
158
|
+
tags = { :UNORDERED_LIST => ['<ul>', '</ul>'],
|
159
|
+
:ORDERED_LIST => ['<ol>', '</ol>'],
|
160
|
+
:DEFINITION_LIST => ['<dl>', '</dl>'],
|
161
|
+
}
|
162
|
+
@list_level += 1
|
163
|
+
body = exec_children node
|
164
|
+
@list_level -= 1
|
165
|
+
if node.kind == :DEFINITION_LIST
|
166
|
+
"#{tags[node.kind][0]}\n#{body.chomp}\n#{tags[node.kind][1]}\n"
|
167
|
+
else
|
168
|
+
body
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
# list_item
|
173
|
+
def list_item(node)
|
174
|
+
marks = { :UL_START => '*', :OL_START => '#' }
|
175
|
+
if node.children.empty?
|
176
|
+
if node.type == :DL_START
|
177
|
+
" <dt>#{exec_list(node.dt)}</dt>\n <dd>#{exec_list(node.value).chomp}</dd>\n"
|
178
|
+
else
|
179
|
+
mark = marks[node.type] * @list_level
|
180
|
+
"#{mark} #{exec_list(node.value).chomp}\n"
|
181
|
+
end
|
182
|
+
else
|
183
|
+
@list_level += 1
|
184
|
+
@p_tag_flag = false
|
185
|
+
str = exec_list(node.value)
|
186
|
+
str += exec_children node
|
187
|
+
str.sub(/\n/, '<br/>')
|
188
|
+
if node.type == :DL_START
|
189
|
+
str = " <dt>#{exec_list(node.dt)}</dt>\n <dd>\n#{str.chomp}</dd>\n"
|
190
|
+
else
|
191
|
+
mark = marks[node.type] * @list_level
|
192
|
+
str = "#{mark} #{str.chomp}\n"
|
193
|
+
end
|
194
|
+
@p_tag_flag = true
|
195
|
+
@list_level -= 1
|
196
|
+
str
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def footnote_link(node)
|
201
|
+
idx = @footnote_idxs.index node.value
|
202
|
+
unless idx
|
203
|
+
idx = @footnote_idxs.size
|
204
|
+
@footnote_idxs << node.value
|
205
|
+
end
|
206
|
+
idx += 1
|
207
|
+
"[#{idx}]"
|
208
|
+
end
|
209
|
+
|
210
|
+
def footnote_define(node)
|
211
|
+
idx = @footnote_idxs.index node.value
|
212
|
+
unless idx
|
213
|
+
idx = @footnote_idxs.size
|
214
|
+
@footnote_idxs << node.value
|
215
|
+
end
|
216
|
+
@footnotes[idx] = exec_children(node)
|
217
|
+
''
|
218
|
+
end
|
219
|
+
|
220
|
+
def footnotes
|
221
|
+
ret = ''
|
222
|
+
@footnotes.each_index{|idx|
|
223
|
+
n = idx+1
|
224
|
+
val = @footnotes[idx]
|
225
|
+
ret += "fn#{n}. #{val}\n"
|
226
|
+
}
|
227
|
+
ret
|
228
|
+
end
|
229
|
+
|
230
|
+
def execute(node)
|
231
|
+
# puts "node:#{node.kind} [#{node.value}]\n"
|
232
|
+
# STDERR.puts node.inspect
|
233
|
+
return '' if node.done?
|
234
|
+
case node.kind
|
235
|
+
when :SECTION
|
236
|
+
str = execute node.headline
|
237
|
+
str += exec_children node # section node
|
238
|
+
when :HEADLINE
|
239
|
+
headline node
|
240
|
+
when :TEXTBLOCK
|
241
|
+
textblock node
|
242
|
+
when :WHITELINE, :WHITELINES
|
243
|
+
"\n" * node.value
|
244
|
+
when :STRING
|
245
|
+
node.value
|
246
|
+
when :QUOTE
|
247
|
+
if node.value == "\n"
|
248
|
+
'<br/>'
|
249
|
+
else
|
250
|
+
node.value
|
251
|
+
end
|
252
|
+
when :TEXTLINE
|
253
|
+
textline node
|
254
|
+
when :EMPHASIS
|
255
|
+
" *#{ exec_children node }* "
|
256
|
+
when :ITALIC
|
257
|
+
" _#{ exec_children node }_ "
|
258
|
+
when :UNDER_LINE
|
259
|
+
%Q| +#{exec_children node}+ |
|
260
|
+
when :STRIKE_THROUGH
|
261
|
+
" -#{ exec_children node}- "
|
262
|
+
when :CODE, :VERBATIM
|
263
|
+
" @#{ exec_children node }@ "
|
264
|
+
when :VARIABLE
|
265
|
+
variable node
|
266
|
+
when :UNORDERED_LIST, :ORDERED_LIST, :DEFINITION_LIST
|
267
|
+
lists node
|
268
|
+
when :LIST_ITEM
|
269
|
+
list_item node
|
270
|
+
when :LINK
|
271
|
+
link node
|
272
|
+
when :BLOCK
|
273
|
+
blocks node
|
274
|
+
when :TABLE
|
275
|
+
table node
|
276
|
+
when :TABLE_ROW
|
277
|
+
table_row node
|
278
|
+
when :FN_LINK
|
279
|
+
footnote_link node
|
280
|
+
when :FN_DEFINE
|
281
|
+
footnote_define node
|
282
|
+
else
|
283
|
+
puts "not implimented=>[#{node.kind}](#{node.inspect})"
|
284
|
+
''
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
# HTML escape
|
289
|
+
def html_escape(s)
|
290
|
+
s.to_s.gsub(/&/, "&").gsub(/>/, ">").gsub(/</, "<")
|
291
|
+
end
|
292
|
+
alias h html_escape
|
293
|
+
|
294
|
+
end
|
295
|
+
|
296
|
+
end
|