facwparser 0.0.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/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +7 -0
- data/facwparser.gemspec +19 -0
- data/lib/facwparser/element.rb +317 -0
- data/lib/facwparser/parser.rb +178 -0
- data/lib/facwparser/render.rb +15 -0
- data/lib/facwparser/version.rb +3 -0
- data/lib/facwparser.rb +9 -0
- data/sample/confluence2html.rb +17 -0
- data/tests/all_tests.rb +3 -0
- data/tests/units/parse/test_add_list_elements.rb +28 -0
- data/tests/units/parse/test_parse1.rb +214 -0
- data/tests/units/parse/test_parse_value.rb +110 -0
- metadata +62 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 HARUYAMA Seigo
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# Facwparser
|
2
|
+
|
3
|
+
Fuxxing Atlassian Confluence Wiki Parser
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'facwparser'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install facwparser
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
TODO: Write usage instructions here
|
22
|
+
|
23
|
+
## Contributing
|
24
|
+
|
25
|
+
1. Fork it
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
29
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/facwparser.gemspec
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'facwparser/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "facwparser"
|
8
|
+
gem.version = Facwparser::VERSION
|
9
|
+
gem.authors = ["HARUYAMA Seigo"]
|
10
|
+
gem.email = ["haruyama@unixuser.org"]
|
11
|
+
gem.description = %q{Fuxxing Atlassian Confluence Wiki Parser}
|
12
|
+
gem.summary = %q{Parser of Atlassian Confluence Wiki Markup.}
|
13
|
+
gem.homepage = "https://github.com/haruyama/facwparser"
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
end
|
@@ -0,0 +1,317 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'cgi'
|
4
|
+
|
5
|
+
|
6
|
+
module Facwparser
|
7
|
+
module Element
|
8
|
+
class ElementBase
|
9
|
+
attr_reader :source, :children
|
10
|
+
def initialize(source)
|
11
|
+
@source = source
|
12
|
+
@children = nil
|
13
|
+
end
|
14
|
+
def ==(other)
|
15
|
+
self.class == other.class && self.source == other.source
|
16
|
+
end
|
17
|
+
|
18
|
+
def render_html(options)
|
19
|
+
raise "TODO: render_html is not implemented: " + self.class.to_s + "\n"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class P < ElementBase
|
24
|
+
def append(source)
|
25
|
+
@source += source
|
26
|
+
end
|
27
|
+
def render_html(options)
|
28
|
+
if source =~ /\A *bq. (.+)/m
|
29
|
+
@children ||= Parser.parse_value($1, options)
|
30
|
+
"<blockquote>\n" + @children.map { |c| c.render_html(options) }.join("") + "\n</blockquote>\n"
|
31
|
+
else
|
32
|
+
@children ||= Parser.parse_value(source, options)
|
33
|
+
"<p>\n" + @children.map { |c| c.render_html(options) }.join("") + "</p>\n"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
class HorizontalRule < ElementBase
|
38
|
+
def render_html(options)
|
39
|
+
"<hr>\n"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
class Heading < ElementBase
|
43
|
+
attr_reader :level, :value
|
44
|
+
def initialize(source, level, value)
|
45
|
+
super(source)
|
46
|
+
@level = level
|
47
|
+
@value = value
|
48
|
+
end
|
49
|
+
def render_html(options)
|
50
|
+
"<h#{level}>#{CGI.escapeHTML value}</h#{level}>\n"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
class List < ElementBase
|
54
|
+
attr_reader :type
|
55
|
+
def initialize(type)
|
56
|
+
super('')
|
57
|
+
@type = type
|
58
|
+
end
|
59
|
+
def push(item)
|
60
|
+
@children ||= []
|
61
|
+
@children.push(item)
|
62
|
+
self
|
63
|
+
end
|
64
|
+
def render_html(options)
|
65
|
+
case @type
|
66
|
+
when '#'
|
67
|
+
return "<ol>\n" + @children.map{ |c| c.render_html(options) }.join("") + "</ol>\n"
|
68
|
+
else
|
69
|
+
return "<ul>\n" + @children.map{ |c| c.render_html(options) }.join("") + "</ul>\n"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
class ListItem < ElementBase
|
74
|
+
attr_reader :symbols, :level, :value
|
75
|
+
def initialize(source, symbols, value)
|
76
|
+
super(source)
|
77
|
+
@symbols = symbols
|
78
|
+
@level = symbols.size
|
79
|
+
@value = value
|
80
|
+
end
|
81
|
+
def render_html(options)
|
82
|
+
@children = Parser.parse_value value, options
|
83
|
+
"<li>" + @children.map {|c| c.render_html(options) }.join("") + "</li>\n"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
class Table < ElementBase
|
87
|
+
def initialize()
|
88
|
+
super('')
|
89
|
+
end
|
90
|
+
def push(item)
|
91
|
+
@children ||= []
|
92
|
+
@children.push(item)
|
93
|
+
self
|
94
|
+
end
|
95
|
+
def render_html(options)
|
96
|
+
"<table><thead>\n" + @children[0].render_html(options) +
|
97
|
+
"\n</thead>\n<tbody>\n" +
|
98
|
+
@children.drop(1).map{|c| c.render_html(options)}.join("\n") +
|
99
|
+
"\n</tbody></table>\n"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
class TableHeaders < ElementBase
|
103
|
+
attr_reader :elements
|
104
|
+
def initialize(source)
|
105
|
+
super(source)
|
106
|
+
@elements = source.split('||')[1..-2]
|
107
|
+
end
|
108
|
+
def render_html(options)
|
109
|
+
"<tr>" +
|
110
|
+
@elements.map { |e| '<th>' + Parser.parse_value(e, options).map { |c| c.render_html(options) }.join(" ") + '</th>'}.join() +
|
111
|
+
"</tr>"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
class TableData < ElementBase
|
115
|
+
attr_reader :elements
|
116
|
+
def initialize(source)
|
117
|
+
super(source)
|
118
|
+
@elements = []
|
119
|
+
element = ''
|
120
|
+
s = StringScanner.new(source[1..-2])
|
121
|
+
in_link = false
|
122
|
+
while s.rest?
|
123
|
+
case
|
124
|
+
when s.scan(/[^|\[\]]+/) || s.scan(/\\\[/) || s.scan(/\\\]/) || s.scan(/\\\|/)
|
125
|
+
element += s[0]
|
126
|
+
when s.scan(/\[/)
|
127
|
+
in_link = true
|
128
|
+
element += s[0]
|
129
|
+
when s.scan(/\]/)
|
130
|
+
in_link = false
|
131
|
+
element += s[0]
|
132
|
+
when s.scan(/\|/)
|
133
|
+
if in_link
|
134
|
+
element += s[0]
|
135
|
+
else
|
136
|
+
@elements << element
|
137
|
+
element = ''
|
138
|
+
end
|
139
|
+
else
|
140
|
+
element += s.rest
|
141
|
+
break
|
142
|
+
end
|
143
|
+
end
|
144
|
+
@elements << element if !element.empty?
|
145
|
+
end
|
146
|
+
def render_html(options)
|
147
|
+
"<tr>" +
|
148
|
+
@elements.map { |e| '<td>' + Parser.parse_value(e, options).map { |c| c.render_html(options) }.join(" ") + '</td>'}.join() +
|
149
|
+
"</tr>"
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
class MacroBase < ElementBase
|
154
|
+
end
|
155
|
+
class TocMacro < MacroBase
|
156
|
+
def initialize(source, options = nil)
|
157
|
+
super(source)
|
158
|
+
@options = options
|
159
|
+
end
|
160
|
+
def render_html(options)
|
161
|
+
"TODO: table of contents Macro\n"
|
162
|
+
end
|
163
|
+
end
|
164
|
+
class PagetreeMacro < MacroBase
|
165
|
+
def initialize(source, options = nil)
|
166
|
+
super(source)
|
167
|
+
@options = options
|
168
|
+
end
|
169
|
+
def render_html(options)
|
170
|
+
"TODO: pagetree macro\n"
|
171
|
+
end
|
172
|
+
end
|
173
|
+
class NoformatMacro < MacroBase
|
174
|
+
def initialize(source, value)
|
175
|
+
super(source)
|
176
|
+
@value = value
|
177
|
+
end
|
178
|
+
def render_html(options)
|
179
|
+
"<pre>\n#{CGI.escapeHTML @value}\n</pre>\n"
|
180
|
+
end
|
181
|
+
end
|
182
|
+
class CodeMacro < MacroBase
|
183
|
+
def initialize(source, options, value)
|
184
|
+
super(source)
|
185
|
+
@options = options
|
186
|
+
@value = value
|
187
|
+
end
|
188
|
+
def render_html(options)
|
189
|
+
"<code class=\"code_#{CGI.escapeHTML(@options[1..-1])}\"><pre>\n#{CGI.escapeHTML @value}\n</pre></code>\n"
|
190
|
+
end
|
191
|
+
end
|
192
|
+
class QuoteMacro < MacroBase
|
193
|
+
def initialize(source, value)
|
194
|
+
super(source)
|
195
|
+
@value = value
|
196
|
+
end
|
197
|
+
def render_html(options)
|
198
|
+
"<blockquote>\n#{CGI.escapeHTML @value}\n</blockquote>\n"
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
class InlineElementBase < ElementBase
|
203
|
+
attr_reader :text
|
204
|
+
def initialize(source, text)
|
205
|
+
super(source)
|
206
|
+
@text = text
|
207
|
+
end
|
208
|
+
def ==(other)
|
209
|
+
super(other) && self.text == other.text
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
class A < InlineElementBase
|
214
|
+
def render_html(options)
|
215
|
+
if @text =~ /\A(.+)\|((?:https?|ftp|file):.+)\z/
|
216
|
+
return '<a href="' + CGI.escapeHTML($2) +'">' + CGI.escapeHTML($1) + '</a>'
|
217
|
+
elsif @text =~ /\A(?:https?|ftp|file):.+\z/
|
218
|
+
return '<a href="' + CGI.escapeHTML(@text) +'">' + CGI.escapeHTML(@text) + '</a>'
|
219
|
+
else
|
220
|
+
return '[' + CGI.escapeHTML(@text) + ']'
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
class Bold < InlineElementBase
|
226
|
+
def render_html(options)
|
227
|
+
"<b>#{CGI.escapeHTML(@text)}</b>"
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
class Italic < InlineElementBase
|
232
|
+
def render_html(options)
|
233
|
+
"<i>#{CGI.escapeHTML(@text)}</i>"
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
class Strike < InlineElementBase
|
238
|
+
def render_html(options)
|
239
|
+
"<s>#{CGI.escapeHTML(@text)}</s>"
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
class Under < InlineElementBase
|
244
|
+
def render_html(options)
|
245
|
+
"<u>#{CGI.escapeHTML(@text)}</u>"
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
class Q < InlineElementBase
|
250
|
+
def render_html(options)
|
251
|
+
"<q>#{CGI.escapeHTML(@text)}</q>"
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
class SUP < InlineElementBase
|
256
|
+
def render_html(options)
|
257
|
+
"<sup>#{CGI.escapeHTML(@text)}</sup>"
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
class SUB < InlineElementBase
|
262
|
+
def render_html(options)
|
263
|
+
"<sub>#{CGI.escapeHTML(@text)}</sub>"
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
class TT < InlineElementBase
|
268
|
+
def render_html(options)
|
269
|
+
"<tt>#{CGI.escapeHTML(@text)}</tt>"
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
class Image < InlineElementBase
|
274
|
+
def render_html(options)
|
275
|
+
'<img src="' + CGI.escapeHTML(@text) + '">'
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
class JiraMacro < MacroBase
|
280
|
+
attr_reader :options
|
281
|
+
def initialize(source, options)
|
282
|
+
super(source)
|
283
|
+
@options = options
|
284
|
+
end
|
285
|
+
def render_html(options)
|
286
|
+
jira_browse_url = (options && options['jira_browse_url']) || ''
|
287
|
+
return '<a href="' + CGI.escapeHTML(jira_browse_url) + CGI.escapeHTML(@options) +'">' + CGI.escapeHTML(@options) + '</a>'
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
class ColorMacroStart < MacroBase
|
292
|
+
attr_reader :options
|
293
|
+
def initialize(source, options)
|
294
|
+
super(source)
|
295
|
+
@options = options
|
296
|
+
end
|
297
|
+
def render_html(options)
|
298
|
+
return '<font color="' + CGI.escapeHTML(@options) +'">'
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
class ColorMacroEnd < MacroBase
|
303
|
+
def initialize(source)
|
304
|
+
super(source)
|
305
|
+
end
|
306
|
+
def render_html(options)
|
307
|
+
return '</font>'
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
class Text < InlineElementBase
|
312
|
+
def render_html(options)
|
313
|
+
CGI.escapeHTML @source
|
314
|
+
end
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end
|
@@ -0,0 +1,178 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'strscan'
|
4
|
+
|
5
|
+
require File.dirname(__FILE__) + '/element'
|
6
|
+
|
7
|
+
module Facwparser
|
8
|
+
module Parser
|
9
|
+
def self.parse(content, options = {})
|
10
|
+
elements = parse1(content, options)
|
11
|
+
process_elements(elements, options)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.process_elements(elements, options)
|
15
|
+
# TODO toc
|
16
|
+
processed = add_list_elements(elements, options)
|
17
|
+
add_table_elements(processed, options)
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.add_list_elements(elements, options)
|
21
|
+
processed = []
|
22
|
+
list_stack = []
|
23
|
+
elements.each { |e|
|
24
|
+
case
|
25
|
+
when e.class == Element::ListItem
|
26
|
+
while list_stack.size > e.level
|
27
|
+
list_stack.pop
|
28
|
+
end
|
29
|
+
while list_stack.size < e.level
|
30
|
+
list = Element::List.new(e.symbols[list_stack.size])
|
31
|
+
if list_stack.empty?
|
32
|
+
processed << list
|
33
|
+
else
|
34
|
+
list_stack[-1].push list
|
35
|
+
end
|
36
|
+
list_stack << list
|
37
|
+
end
|
38
|
+
list_stack[-1].push e
|
39
|
+
else
|
40
|
+
list_stack.clear if list_stack.size > 0
|
41
|
+
processed << e
|
42
|
+
end
|
43
|
+
}
|
44
|
+
processed
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.add_table_elements(elements, options)
|
48
|
+
processed = []
|
49
|
+
table = nil
|
50
|
+
elements.each { |e|
|
51
|
+
case
|
52
|
+
when e.class == Element::TableHeaders || e.class == Element::TableData
|
53
|
+
if !table
|
54
|
+
table = Element::Table.new
|
55
|
+
processed << table
|
56
|
+
end
|
57
|
+
table.push e
|
58
|
+
else
|
59
|
+
table = nil
|
60
|
+
processed << e
|
61
|
+
end
|
62
|
+
}
|
63
|
+
processed
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.parse1(content, options)
|
67
|
+
s = StringScanner.new(content.gsub("\r", '').gsub(/[\t\f]/, ' ') + "\n")
|
68
|
+
|
69
|
+
elements = []
|
70
|
+
|
71
|
+
p = nil
|
72
|
+
|
73
|
+
while s.rest?
|
74
|
+
case
|
75
|
+
when s.scan(/h(\d)\. +(.+)\n/)
|
76
|
+
p = nil
|
77
|
+
elements << Element::Heading.new(s[0], s[1].to_i, s[2])
|
78
|
+
when s.scan(/----+\n/)
|
79
|
+
p = nil
|
80
|
+
elements << Element::HorizontalRule.new(s[0])
|
81
|
+
when s.scan(/([*\-#]+) +(.+)\n/)
|
82
|
+
p = nil
|
83
|
+
elements << Element::ListItem.new(s[0], s[1], s[2])
|
84
|
+
when s.scan(/\|\|.+\|\| *\n/)
|
85
|
+
p = nil
|
86
|
+
elements << Element::TableHeaders.new(s[0])
|
87
|
+
when s.scan(/\|.+\| *\n/)
|
88
|
+
p = nil
|
89
|
+
elements << Element::TableData.new(s[0])
|
90
|
+
when s.scan(/\{toc(:.*)?\} *\n/)
|
91
|
+
p = nil
|
92
|
+
elements << Element::TocMacro.new(s[0], s[1] ? s[1][1,] : nil)
|
93
|
+
when s.scan(/\{pagetree(:.*)?\} *\n/)
|
94
|
+
p = nil
|
95
|
+
elements << Element::PagetreeMacro.new(s[0], s[1] ? s[1][1,] : nil)
|
96
|
+
when s.scan(/\{noformat\} *\n(?m)(.+?\n)\{noformat\} *\n/)
|
97
|
+
p = nil
|
98
|
+
elements << Element::NoformatMacro.new(s[0], s[1])
|
99
|
+
when s.scan(/\{code(:.*?)?\} *\n(?m)(.+?\n)\{code\} *\n/)
|
100
|
+
p = nil
|
101
|
+
elements << Element::CodeMacro.new(s[0], s[1], s[2])
|
102
|
+
when s.scan(/\{quote\} *\n(?m)(.+?\n)\{quote\} *\n/)
|
103
|
+
p = nil
|
104
|
+
elements << Element::QuoteMacro.new(s[0], s[1])
|
105
|
+
when s.scan(/ *\n/)
|
106
|
+
p = nil
|
107
|
+
when s.scan(/(.+)\n/)
|
108
|
+
if p
|
109
|
+
p.append(s[0])
|
110
|
+
else
|
111
|
+
p = Element::P.new(s[0])
|
112
|
+
elements << p
|
113
|
+
end
|
114
|
+
else
|
115
|
+
raise "Parse Error. line=#{s.rest}"
|
116
|
+
end
|
117
|
+
end
|
118
|
+
elements
|
119
|
+
end
|
120
|
+
|
121
|
+
def self.unescape_text(text)
|
122
|
+
text.gsub(/\\([\[\]\*+_?{}!^~-])/) {
|
123
|
+
$1
|
124
|
+
}
|
125
|
+
end
|
126
|
+
|
127
|
+
def self.parse_value(value, options)
|
128
|
+
|
129
|
+
#jira
|
130
|
+
#img
|
131
|
+
|
132
|
+
children = []
|
133
|
+
|
134
|
+
value.split("\n").each { |l|
|
135
|
+
s = StringScanner.new(l)
|
136
|
+
while s.rest?
|
137
|
+
case
|
138
|
+
when s.scan(/\[(.+?)(?<!\\)\]/)
|
139
|
+
children << Element::A.new(s[0], unescape_text(s[1]))
|
140
|
+
when s.scan(/\*(.+?)(?<!\\)\*/)
|
141
|
+
children << Element::Bold.new(s[0], unescape_text(s[1]))
|
142
|
+
when s.scan(/\_(.+?)(?<!\\)\_/)
|
143
|
+
children << Element::Italic.new(s[0], unescape_text(s[1]))
|
144
|
+
when s.scan(/\-(.+?)(?<!\\)\-/)
|
145
|
+
children << Element::Strike.new(s[0], unescape_text(s[1]))
|
146
|
+
when s.scan(/\+(.+?)(?<!\\)\+/)
|
147
|
+
children << Element::Under.new(s[0], unescape_text(s[1]))
|
148
|
+
when s.scan(/\^(.+?)(?<!\\)\^/)
|
149
|
+
children << Element::SUP.new(s[0], unescape_text(s[1]))
|
150
|
+
when s.scan(/\~(.+?)(?<!\\)\~/)
|
151
|
+
children << Element::SUB.new(s[0], unescape_text(s[1]))
|
152
|
+
when s.scan(/\?\?(.+?)(?<!\\)\?\?/)
|
153
|
+
children << Element::Q.new(s[0], unescape_text(s[1]))
|
154
|
+
when s.scan(/\{\{(.+?)(?<!\\)\}\}/)
|
155
|
+
children << Element::TT.new(s[0], unescape_text(s[1]))
|
156
|
+
when s.scan(/\!(https?:(?:.+?))(?<!\\)\!/)
|
157
|
+
children << Element::Image.new(s[0], unescape_text(s[1]))
|
158
|
+
when s.scan(/\{jira:(.+?)(?<!\\)\}/)
|
159
|
+
children << Element::JiraMacro.new(s[0], unescape_text(s[1]))
|
160
|
+
when s.scan(/\{color:(.+?)(?<!\\)\}/)
|
161
|
+
children << Element::ColorMacroStart.new(s[0], unescape_text(s[1]))
|
162
|
+
when s.scan(/\{color\}/)
|
163
|
+
children << Element::ColorMacroEnd.new(s[0])
|
164
|
+
when s.scan(/[^\[^\\*_+{!-]+/)
|
165
|
+
children << Element::Text.new(s[0], unescape_text(s[0]))
|
166
|
+
when s.scan(/\\[\[\]\*+_?{}!^~-]/)
|
167
|
+
children << Element::Text.new(s[0], unescape_text(s[0]))
|
168
|
+
else
|
169
|
+
children << Element::Text.new(s.rest, unescape_text(s.rest))
|
170
|
+
break
|
171
|
+
end
|
172
|
+
end
|
173
|
+
}
|
174
|
+
|
175
|
+
children
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require File.dirname(__FILE__) + '/element'
|
4
|
+
|
5
|
+
module Facwparser
|
6
|
+
module Render
|
7
|
+
def self.render_html(elements, options = {})
|
8
|
+
output = ''
|
9
|
+
elements.each { |e|
|
10
|
+
output += e.render_html(options)
|
11
|
+
}
|
12
|
+
output
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/facwparser.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# -*- encoding: utf-8 -*-
|
3
|
+
require 'facwparser'
|
4
|
+
|
5
|
+
print <<EOS
|
6
|
+
<!DOCTYPE html>
|
7
|
+
<html>
|
8
|
+
<head>
|
9
|
+
<title>sample</title>
|
10
|
+
</head>
|
11
|
+
<body>
|
12
|
+
EOS
|
13
|
+
print Facwparser.to_html(ARGF.read)
|
14
|
+
print <<EOS
|
15
|
+
</body>
|
16
|
+
</html>
|
17
|
+
EOS
|
data/tests/all_tests.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require 'test/unit'
|
3
|
+
require File.dirname(__FILE__) + '/../../../lib/facwparser/parser'
|
4
|
+
|
5
|
+
|
6
|
+
class TestAddListElements < Test::Unit::TestCase
|
7
|
+
|
8
|
+
def test_add_list_elements_1
|
9
|
+
pre = [
|
10
|
+
Facwparser::Element::ListItem.new("* 4\n", '*', '4'),
|
11
|
+
Facwparser::Element::ListItem.new("** 5\n", '**', '5'),
|
12
|
+
Facwparser::Element::ListItem.new("**# 6\n", '**#', '6'),
|
13
|
+
]
|
14
|
+
|
15
|
+
assert_equal([
|
16
|
+
Facwparser::Element::List.new('*').push(
|
17
|
+
Facwparser::Element::ListItem.new("* 4\n", '*', '4')).push(
|
18
|
+
Facwparser::Element::List.new('*').push(
|
19
|
+
Facwparser::Element::ListItem.new("** 5\n", '**', '5')).push(
|
20
|
+
Facwparser::Element::List.new('#').push(
|
21
|
+
Facwparser::Element::ListItem.new("**# 6\n", '**#', '6'))
|
22
|
+
))
|
23
|
+
], Facwparser::Parser.add_list_elements(pre, {}))
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
@@ -0,0 +1,214 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require 'test/unit'
|
3
|
+
require File.dirname(__FILE__) + '/../../../lib/facwparser/parser'
|
4
|
+
|
5
|
+
|
6
|
+
class TestParse1 < Test::Unit::TestCase
|
7
|
+
|
8
|
+
def test_parse1_p
|
9
|
+
source =<<EOS
|
10
|
+
ほげ
|
11
|
+
|
12
|
+
ほげほげ
|
13
|
+
|
14
|
+
ほげほげげほ
|
15
|
+
にゃ
|
16
|
+
EOS
|
17
|
+
assert_equal(
|
18
|
+
[
|
19
|
+
Facwparser::Element::P.new("ほげ\n"),
|
20
|
+
Facwparser::Element::P.new("ほげほげ\n"),
|
21
|
+
Facwparser::Element::P.new("ほげほげげほ\nにゃ\n"),
|
22
|
+
],
|
23
|
+
Facwparser::Parser.parse1(source, {}))
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_parse1_heading
|
28
|
+
source =<<EOS
|
29
|
+
h1. ほげ
|
30
|
+
|
31
|
+
ほげほげ
|
32
|
+
h3. ほげほげげほ
|
33
|
+
にゃ
|
34
|
+
EOS
|
35
|
+
assert_equal(
|
36
|
+
[
|
37
|
+
Facwparser::Element::Heading.new("h1. ほげ\n", 1, "ほげ"),
|
38
|
+
Facwparser::Element::P.new("ほげほげ\n"),
|
39
|
+
Facwparser::Element::Heading.new("h3. ほげほげげほ\n", 3, "ほげほげげほ"),
|
40
|
+
Facwparser::Element::P.new("にゃ\n"),
|
41
|
+
],
|
42
|
+
Facwparser::Parser.parse1(source, {}))
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_parse1_horizontal_rule
|
47
|
+
source =<<EOS
|
48
|
+
1
|
49
|
+
----
|
50
|
+
2
|
51
|
+
---
|
52
|
+
3
|
53
|
+
|
54
|
+
-----
|
55
|
+
4
|
56
|
+
EOS
|
57
|
+
assert_equal(
|
58
|
+
[
|
59
|
+
Facwparser::Element::P.new("1\n"),
|
60
|
+
Facwparser::Element::HorizontalRule.new("----\n"),
|
61
|
+
Facwparser::Element::P.new("2\n---\n3\n"),
|
62
|
+
Facwparser::Element::HorizontalRule.new("-----\n"),
|
63
|
+
Facwparser::Element::P.new("4\n"),
|
64
|
+
],
|
65
|
+
Facwparser::Parser.parse1(source, {}))
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
def test_parse1_list_item
|
70
|
+
source =<<EOS
|
71
|
+
1
|
72
|
+
- 2
|
73
|
+
3
|
74
|
+
** 4
|
75
|
+
### 5
|
76
|
+
#*#* 6
|
77
|
+
EOS
|
78
|
+
assert_equal(
|
79
|
+
[
|
80
|
+
Facwparser::Element::P.new("1\n"),
|
81
|
+
Facwparser::Element::ListItem.new("- 2\n", '-', '2'),
|
82
|
+
Facwparser::Element::P.new("3\n"),
|
83
|
+
Facwparser::Element::ListItem.new("** 4\n", '**', '4'),
|
84
|
+
Facwparser::Element::ListItem.new("### 5\n", '###', '5'),
|
85
|
+
Facwparser::Element::ListItem.new("#*#* 6\n", '#*#*', '6'),
|
86
|
+
],
|
87
|
+
Facwparser::Parser.parse1(source, {}))
|
88
|
+
|
89
|
+
end
|
90
|
+
|
91
|
+
def test_parse1_table
|
92
|
+
source =<<EOS
|
93
|
+
1
|
94
|
+
||2||3||
|
95
|
+
|4|5|
|
96
|
+
6
|
97
|
+
EOS
|
98
|
+
assert_equal(
|
99
|
+
[
|
100
|
+
Facwparser::Element::P.new("1\n"),
|
101
|
+
Facwparser::Element::TableHeaders.new("||2||3||\n"),
|
102
|
+
Facwparser::Element::TableData.new("|4|5|\n"),
|
103
|
+
Facwparser::Element::P.new("6\n"),
|
104
|
+
],
|
105
|
+
Facwparser::Parser.parse1(source, {}))
|
106
|
+
|
107
|
+
end
|
108
|
+
|
109
|
+
def test_parse1_toc_1
|
110
|
+
source =<<EOS
|
111
|
+
1
|
112
|
+
{toc}
|
113
|
+
2
|
114
|
+
EOS
|
115
|
+
assert_equal(
|
116
|
+
[
|
117
|
+
Facwparser::Element::P.new("1\n"),
|
118
|
+
Facwparser::Element::TocMacro.new("{toc}\n"),
|
119
|
+
Facwparser::Element::P.new("2\n"),
|
120
|
+
],
|
121
|
+
Facwparser::Parser.parse1(source, {}))
|
122
|
+
|
123
|
+
end
|
124
|
+
|
125
|
+
def test_parse1_toc_2
|
126
|
+
source =<<EOS
|
127
|
+
1
|
128
|
+
{toc:maxLevel=3}
|
129
|
+
2
|
130
|
+
EOS
|
131
|
+
assert_equal(
|
132
|
+
[
|
133
|
+
Facwparser::Element::P.new("1\n"),
|
134
|
+
Facwparser::Element::TocMacro.new("{toc:maxLevel=3}\n", 'maxLevel=3'),
|
135
|
+
Facwparser::Element::P.new("2\n"),
|
136
|
+
],
|
137
|
+
Facwparser::Parser.parse1(source, {}))
|
138
|
+
|
139
|
+
end
|
140
|
+
|
141
|
+
def test_parse1_pagetree
|
142
|
+
source =<<EOS
|
143
|
+
1
|
144
|
+
{pagetree:root=@self}
|
145
|
+
2
|
146
|
+
EOS
|
147
|
+
assert_equal(
|
148
|
+
[
|
149
|
+
Facwparser::Element::P.new("1\n"),
|
150
|
+
Facwparser::Element::PagetreeMacro.new("{pagetree:root=@self}\n", 'root=@self'),
|
151
|
+
Facwparser::Element::P.new("2\n"),
|
152
|
+
],
|
153
|
+
Facwparser::Parser.parse1(source, {}))
|
154
|
+
|
155
|
+
end
|
156
|
+
|
157
|
+
def test_parse1_noformat
|
158
|
+
source =<<EOS
|
159
|
+
1
|
160
|
+
{noformat}
|
161
|
+
2
|
162
|
+
3
|
163
|
+
{noformat}
|
164
|
+
4
|
165
|
+
EOS
|
166
|
+
assert_equal(
|
167
|
+
[
|
168
|
+
Facwparser::Element::P.new("1\n"),
|
169
|
+
Facwparser::Element::NoformatMacro.new("{noformat}\n2\n3\n{noformat}\n", "2\n3\n"),
|
170
|
+
Facwparser::Element::P.new("4\n"),
|
171
|
+
],
|
172
|
+
Facwparser::Parser.parse1(source, {}))
|
173
|
+
|
174
|
+
end
|
175
|
+
|
176
|
+
def test_parse1_code
|
177
|
+
source =<<EOS
|
178
|
+
1
|
179
|
+
{code:ruby}
|
180
|
+
a = 1 + 2
|
181
|
+
3
|
182
|
+
{code}
|
183
|
+
4
|
184
|
+
EOS
|
185
|
+
assert_equal(
|
186
|
+
[
|
187
|
+
Facwparser::Element::P.new("1\n"),
|
188
|
+
Facwparser::Element::CodeMacro.new("{code:ruby}\na = 1 + 2\n3\n{code}\n", 'ruby', "a = 1 + 2\n3\n"),
|
189
|
+
Facwparser::Element::P.new("4\n"),
|
190
|
+
],
|
191
|
+
Facwparser::Parser.parse1(source, {}))
|
192
|
+
|
193
|
+
end
|
194
|
+
|
195
|
+
def test_parse1_quote
|
196
|
+
source =<<EOS
|
197
|
+
1
|
198
|
+
{quote}
|
199
|
+
2
|
200
|
+
3
|
201
|
+
{quote}
|
202
|
+
4
|
203
|
+
EOS
|
204
|
+
assert_equal(
|
205
|
+
[
|
206
|
+
Facwparser::Element::P.new("1\n"),
|
207
|
+
Facwparser::Element::QuoteMacro.new("{quote}\n2\n3\n{quote}\n", "2\n3\n"),
|
208
|
+
Facwparser::Element::P.new("4\n"),
|
209
|
+
],
|
210
|
+
Facwparser::Parser.parse1(source, {}))
|
211
|
+
|
212
|
+
end
|
213
|
+
|
214
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require 'test/unit'
|
3
|
+
require File.dirname(__FILE__) + '/../../../lib/facwparser/parser'
|
4
|
+
|
5
|
+
|
6
|
+
class TestParseValue < Test::Unit::TestCase
|
7
|
+
|
8
|
+
def test_parse_value_text
|
9
|
+
assert_equal([
|
10
|
+
Facwparser::Element::Text.new('1', '1')
|
11
|
+
], Facwparser::Parser.parse_value('1', {}))
|
12
|
+
assert_equal([
|
13
|
+
Facwparser::Element::Text.new('1', '1'),
|
14
|
+
Facwparser::Element::Text.new('2', '2')
|
15
|
+
], Facwparser::Parser.parse_value("1\n2", {}))
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_parse_value_a
|
19
|
+
assert_equal([
|
20
|
+
Facwparser::Element::Text.new('1', '1'),
|
21
|
+
Facwparser::Element::A.new('[hoge]', 'hoge'),
|
22
|
+
Facwparser::Element::Text.new('2', '2')
|
23
|
+
], Facwparser::Parser.parse_value('1[hoge]2', {}))
|
24
|
+
assert_equal([
|
25
|
+
Facwparser::Element::Text.new('1', '1'),
|
26
|
+
Facwparser::Element::A.new('[hoge\]]', 'hoge]'),
|
27
|
+
Facwparser::Element::Text.new('2', '2')
|
28
|
+
], Facwparser::Parser.parse_value('1[hoge\]]2', {}))
|
29
|
+
assert_equal([
|
30
|
+
Facwparser::Element::A.new('[株式会社ミクシィ|https://mixi.co.jp/]', '株式会社ミクシィ|https://mixi.co.jp/'),
|
31
|
+
], Facwparser::Parser.parse_value('[株式会社ミクシィ|https://mixi.co.jp/]', {}))
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_parse_value_bold
|
35
|
+
assert_equal([
|
36
|
+
Facwparser::Element::Text.new('1', '1'),
|
37
|
+
Facwparser::Element::Bold.new('*hoge*', 'hoge'),
|
38
|
+
Facwparser::Element::Text.new('2', '2')
|
39
|
+
], Facwparser::Parser.parse_value('1*hoge*2', {}))
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_parse_value_italic
|
43
|
+
assert_equal([
|
44
|
+
Facwparser::Element::Text.new('1', '1'),
|
45
|
+
Facwparser::Element::Italic.new('_hoge_', 'hoge'),
|
46
|
+
Facwparser::Element::Text.new('2', '2')
|
47
|
+
], Facwparser::Parser.parse_value('1_hoge_2', {}))
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_parse_value_strike
|
51
|
+
assert_equal([
|
52
|
+
Facwparser::Element::Text.new('1', '1'),
|
53
|
+
Facwparser::Element::Strike.new('-hoge\-i-', 'hoge-i'),
|
54
|
+
Facwparser::Element::Text.new('2', '2')
|
55
|
+
], Facwparser::Parser.parse_value('1-hoge\-i-2', {}))
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_parse_value_under
|
59
|
+
assert_equal([
|
60
|
+
Facwparser::Element::Text.new('1', '1'),
|
61
|
+
Facwparser::Element::Under.new('+hoge+', 'hoge'),
|
62
|
+
Facwparser::Element::Text.new('2', '2')
|
63
|
+
], Facwparser::Parser.parse_value('1+hoge+2', {}))
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_parse_value_image
|
67
|
+
assert_equal([
|
68
|
+
Facwparser::Element::Text.new('1', '1'),
|
69
|
+
Facwparser::Element::Image.new('!http://www.unixuser.org/!', 'http://www.unixuser.org/'),
|
70
|
+
Facwparser::Element::Text.new('2', '2')
|
71
|
+
], Facwparser::Parser.parse_value('1!http://www.unixuser.org/!2', {}))
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_parse_value_jira_macro
|
75
|
+
assert_equal([
|
76
|
+
Facwparser::Element::Text.new('1', '1'),
|
77
|
+
Facwparser::Element::JiraMacro.new('{jira:SYSTEMRD-1}', 'SYSTEMRD-1'),
|
78
|
+
Facwparser::Element::Text.new('2', '2')
|
79
|
+
], Facwparser::Parser.parse_value('1{jira:SYSTEMRD-1}2', {}))
|
80
|
+
end
|
81
|
+
|
82
|
+
def test_parse_value_jira_misc
|
83
|
+
assert_equal([
|
84
|
+
Facwparser::Element::Text.new('1', '1'),
|
85
|
+
Facwparser::Element::Text.new('{jira:SYSTEMRD-1', '{jira:SYSTEMRD-1'),
|
86
|
+
], Facwparser::Parser.parse_value('1{jira:SYSTEMRD-1', {}))
|
87
|
+
|
88
|
+
assert_equal([
|
89
|
+
Facwparser::Element::Text.new('1', '1'),
|
90
|
+
Facwparser::Element::Text.new('{jira:SYSTEMRD\-1', '{jira:SYSTEMRD-1'),
|
91
|
+
], Facwparser::Parser.parse_value('1{jira:SYSTEMRD\-1', {}))
|
92
|
+
|
93
|
+
assert_equal([
|
94
|
+
Facwparser::Element::Text.new('1', '1'),
|
95
|
+
Facwparser::Element::Text.new('\{', '{'),
|
96
|
+
Facwparser::Element::Text.new('jira:SYSTEMRD', 'jira:SYSTEMRD'),
|
97
|
+
Facwparser::Element::Text.new('\\-', '-'),
|
98
|
+
Facwparser::Element::Text.new('1', '1'),
|
99
|
+
], Facwparser::Parser.parse_value('1\\{jira:SYSTEMRD\\-1', {}))
|
100
|
+
|
101
|
+
assert_equal([
|
102
|
+
Facwparser::Element::Text.new('1', '1'),
|
103
|
+
Facwparser::Element::Text.new('\{', '{'),
|
104
|
+
Facwparser::Element::JiraMacro.new('{jira:SYSTEMRD\-1}', 'SYSTEMRD-1'),
|
105
|
+
], Facwparser::Parser.parse_value('1\\{{jira:SYSTEMRD\\-1}', {}))
|
106
|
+
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
|
metadata
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: facwparser
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- HARUYAMA Seigo
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-02-08 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: Fuxxing Atlassian Confluence Wiki Parser
|
15
|
+
email:
|
16
|
+
- haruyama@unixuser.org
|
17
|
+
executables: []
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- .gitignore
|
22
|
+
- Gemfile
|
23
|
+
- LICENSE.txt
|
24
|
+
- README.md
|
25
|
+
- Rakefile
|
26
|
+
- facwparser.gemspec
|
27
|
+
- lib/facwparser.rb
|
28
|
+
- lib/facwparser/element.rb
|
29
|
+
- lib/facwparser/parser.rb
|
30
|
+
- lib/facwparser/render.rb
|
31
|
+
- lib/facwparser/version.rb
|
32
|
+
- sample/confluence2html.rb
|
33
|
+
- tests/all_tests.rb
|
34
|
+
- tests/units/parse/test_add_list_elements.rb
|
35
|
+
- tests/units/parse/test_parse1.rb
|
36
|
+
- tests/units/parse/test_parse_value.rb
|
37
|
+
homepage: https://github.com/haruyama/facwparser
|
38
|
+
licenses: []
|
39
|
+
post_install_message:
|
40
|
+
rdoc_options: []
|
41
|
+
require_paths:
|
42
|
+
- lib
|
43
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
44
|
+
none: false
|
45
|
+
requirements:
|
46
|
+
- - ! '>='
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
49
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
requirements: []
|
56
|
+
rubyforge_project:
|
57
|
+
rubygems_version: 1.8.24
|
58
|
+
signing_key:
|
59
|
+
specification_version: 3
|
60
|
+
summary: Parser of Atlassian Confluence Wiki Markup.
|
61
|
+
test_files: []
|
62
|
+
has_rdoc:
|