wikicloth 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,81 @@
1
+ Ruby implementation of the MediaWiki markup language.
2
+
3
+ Supports
4
+ ---------------------------------------------------
5
+
6
+ * Variables, Templates {{ ... }}
7
+ * Links
8
+ o External Links [ ... ]
9
+ o Internal Links, Images [[ ... ]]
10
+ + (see also pipe trick)
11
+ * Wikimedia Markup
12
+ o == Headings ==
13
+ o Lists (*#;:)
14
+ o bold ('''), italic ('') or both (''''')
15
+ o Horizontal rule (----)
16
+ o Tables
17
+ * <code>,<nowiki>,<pre> (disable wiki markup)
18
+ o space at the beginning of a line (<pre>)
19
+ * <ref> and <references/> support
20
+ * html sanitization
21
+
22
+ Install
23
+ ---------------------------------------------------
24
+
25
+ git clone git://github.com/nricciar/wikicloth.git
26
+ cd wikicloth/
27
+ gem build wikicloth.gemspec
28
+ sudo gem install wikicloth-0.1.3.gem
29
+
30
+ Usage
31
+ ---------------------------------------------------
32
+
33
+ include WikiCloth
34
+
35
+ @wiki = WikiCloth.new({
36
+ :data => "<nowiki>{{test}}</nowiki> ''Hello {{test}}!''\n",
37
+ :params => { "test" => "World" } })
38
+ @wiki.to_html => "<p>&#123;&#123;test&#125;&#125; <i>Hello World!</i></p>"
39
+
40
+
41
+ Wiki Links and Variable/Template Handling
42
+ ---------------------------------------------------
43
+
44
+ Use the url_for and link_attributes_for methods to override the default URL for
45
+ an [[internal link]]. If you need even more control, the link_for can also be
46
+ used to return raw html.
47
+
48
+ class CustomLinkHandler < WikiCloth::WikiLinkHandler
49
+
50
+ def url_for(page)
51
+ "javascript:alert('You clicked on: #{page}');"
52
+ end
53
+
54
+ def link_attributes_for(page)
55
+ { :href => url_for(page) }
56
+ end
57
+
58
+ def include_resource(resource,options=[])
59
+ case resource
60
+ when "date"
61
+ Time.now.to_s
62
+ else
63
+ # default behavior
64
+ super(resource,options)
65
+ end
66
+ end
67
+
68
+ end
69
+
70
+
71
+ @wiki = WikiCloth::WikiCloth.new({
72
+ :params => { "PAGENAME" => "Testing123" },
73
+ :link_handler => CustomLinkHandler.new,
74
+ :data => "Hello World From {{ PAGENAME }} on {{ date }}\n"
75
+ })
76
+
77
+ @wiki.to_html =>
78
+ <p>
79
+ <a href="javascript:alert('You clicked on: Hello World');">Hello World</a> From Testing123 on Wed Jul 08 22:23:44 -0400 2009
80
+ </p>
81
+
data/Rakefile ADDED
@@ -0,0 +1,23 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ desc 'Default: run unit tests.'
6
+ task :default => :test
7
+
8
+ desc 'Test the wikicloth plugin.'
9
+ Rake::TestTask.new(:test) do |t|
10
+ t.libs << 'lib'
11
+ t.libs << 'test'
12
+ t.pattern = 'test/**/*_test.rb'
13
+ t.verbose = true
14
+ end
15
+
16
+ desc 'Generate documentation for the wikicloth plugin.'
17
+ Rake::RDocTask.new(:rdoc) do |rdoc|
18
+ rdoc.rdoc_dir = 'rdoc'
19
+ rdoc.title = 'WikiCloth'
20
+ rdoc.options << '--line-numbers' << '--inline-source'
21
+ rdoc.rdoc_files.include('README')
22
+ rdoc.rdoc_files.include('lib/**/*.rb')
23
+ end
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require File.join(File.expand_path(File.dirname(__FILE__)), "lib", "wikicloth")
data/lib/core_ext.rb ADDED
@@ -0,0 +1,43 @@
1
+ module ExtendedString
2
+
3
+ def blank?
4
+ respond_to?(:empty?) ? empty? : !self
5
+ end
6
+
7
+ def to_slug
8
+ self.gsub(/\W+/, '-').gsub(/^-+/,'').gsub(/-+$/,'').downcase
9
+ end
10
+
11
+ def auto_link
12
+ url_check = Regexp.new( '(^|[\n ])([\w]+?://[\w]+[^ \"\r\n\t<]*)', Regexp::MULTILINE | Regexp::IGNORECASE )
13
+ www_check = Regexp.new( '(^|[\n ])((www)\.[^ \"\t\n\r<]*)', Regexp::MULTILINE | Regexp::IGNORECASE )
14
+ self.gsub!(url_check, '\1<a href="\2">\2</a>')
15
+ self.gsub!(www_check, '\1<a href="http://\2">\2</a>')
16
+ to_s
17
+ end
18
+
19
+ def dump()
20
+ ret = to_s
21
+ delete!(to_s)
22
+ ret
23
+ end
24
+
25
+ def smart_split(char)
26
+ ret = []
27
+ tmp = ""
28
+ inside = 0
29
+ to_s.each_char do |x|
30
+ if x == char && inside == 0
31
+ ret << tmp
32
+ tmp = ""
33
+ else
34
+ inside += 1 if x == "[" || x == "{" || x == "<"
35
+ inside -= 1 if x == "]" || x == "}" || x == ">"
36
+ tmp += x
37
+ end
38
+ end
39
+ ret << tmp unless tmp.empty?
40
+ ret
41
+ end
42
+
43
+ end
@@ -0,0 +1,237 @@
1
+ require 'rubygems'
2
+ require 'builder'
3
+
4
+ module WikiCloth
5
+
6
+ class WikiBuffer::HTMLElement < WikiBuffer
7
+
8
+ ALLOWED_ELEMENTS = ['a','b','i','div','span','sup','sub','strike','s','u','font','big','ref','tt','del',
9
+ 'small','blockquote','strong','pre','code','references','ol','li','ul','dd','dt','dl','center',
10
+ 'h2','h3','h4','h5','h6']
11
+ ALLOWED_ATTRIBUTES = ['id','name','style','class','href','start','value']
12
+ ESCAPED_TAGS = [ 'nowiki', 'pre', 'code' ]
13
+ SHORT_TAGS = [ 'meta','br','hr','img' ]
14
+ NO_NEED_TO_CLOSE = ['li','p'] + SHORT_TAGS
15
+
16
+ def initialize(d="",options={},check=nil)
17
+ super("",options)
18
+ self.buffer_type = "Element"
19
+ @in_quotes = false
20
+ @in_single_quotes = false
21
+ @start_tag = 1
22
+ @tag_check = check unless check.nil?
23
+ end
24
+
25
+ def run_globals?
26
+ return ESCAPED_TAGS.include?(self.element_name) ? false : true
27
+ end
28
+
29
+ def to_s
30
+ if NO_NEED_TO_CLOSE.include?(self.element_name)
31
+ return "<#{self.element_name} />" if SHORT_TAGS.include?(self.element_name)
32
+ return "</#{self.element_name}><#{self.element_name}>" if @tag_check == self.element_name
33
+ end
34
+
35
+ if ESCAPED_TAGS.include?(self.element_name)
36
+ # escape all html inside this element
37
+ self.element_content = self.element_content.gsub('<','&lt;').gsub('>','&gt;')
38
+ # hack to fix <code><nowiki> nested mess
39
+ self.element_content = self.element_content.gsub(/&lt;[\/]*\s*nowiki\s*&gt;/,'')
40
+ end
41
+
42
+ lhandler = @options[:link_handler]
43
+ case self.element_name
44
+ when "ref"
45
+ self.element_name = "sup"
46
+ named_ref = self.name_attribute
47
+ ref = lhandler.find_reference_by_name(named_ref) unless named_ref.nil?
48
+ if ref.nil?
49
+ lhandler.references << { :name => named_ref, :value => self.element_content, :count => 0 }
50
+ ref = lhandler.references.last
51
+ end
52
+ ref_id = (named_ref.nil? ? "" : "#{named_ref}_") + "#{lhandler.reference_index(ref)}-#{ref[:count]}"
53
+ self.params << { :name => "id", :value => "cite_ref-#{ref_id}" }
54
+ self.params << { :name => "class", :value => "reference" }
55
+ self.element_content = "[<a href=\"#cite_note-" + (named_ref.nil? ? "" : "#{named_ref}_") +
56
+ "#{lhandler.reference_index(ref)}\">#{lhandler.reference_index(ref)}</a>]"
57
+ ref[:count] += 1
58
+ when "references"
59
+ ref_count = 0
60
+ self.element_name = "ol"
61
+ self.element_content = lhandler.references.collect { |r|
62
+ ref_count += 1
63
+ ref_name = (r[:name].nil? ? "" : r[:name].to_slug + "_")
64
+ ret = "<li id=\"cite_note-#{ref_name}#{ref_count}\"><b>"
65
+ 1.upto(r[:count]) { |x| ret += "<a href=\"#cite_ref-#{ref_name}#{ref_count}-#{x-1}\">" +
66
+ (r[:count] == 1 ? "^" : (x-1).to_s(26).tr('0-9a-p', 'a-z')) + "</a> " }
67
+ ret += "</b> #{r[:value]}</li>"
68
+ }.to_s
69
+ when "nowiki"
70
+ return self.element_content
71
+ end
72
+
73
+ tmp = elem.tag!(self.element_name, self.element_attributes) { |x| x << self.element_content }
74
+ unless ALLOWED_ELEMENTS.include?(self.element_name)
75
+ tmp.gsub!(/[\-!\|&"\{\}\[\]]/) { |r| self.escape_char(r) }
76
+ return tmp.gsub('<', '&lt;').gsub('>', '&gt;')
77
+ end
78
+ tmp
79
+ end
80
+
81
+ def name_attribute
82
+ params.each { |p| return p[:value].to_slug if p.kind_of?(Hash) && p[:name] == "name" }
83
+ return nil
84
+ end
85
+
86
+ def element_attributes
87
+ attr = {}
88
+ params.each { |p| attr[p[:name]] = p[:value] if p.kind_of?(Hash) }
89
+ if ALLOWED_ELEMENTS.include?(self.element_name.strip.downcase)
90
+ attr.delete_if { |key,value| !ALLOWED_ATTRIBUTES.include?(key.strip) }
91
+ end
92
+ return attr
93
+ end
94
+
95
+ def element_name
96
+ @ename ||= ""
97
+ end
98
+
99
+ def element_content
100
+ @econtent ||= ""
101
+ end
102
+
103
+ protected
104
+
105
+ def escape_char(c)
106
+ c = case c
107
+ when '-' then '&#45;'
108
+ when '!' then '&#33;'
109
+ when '|' then '&#124;'
110
+ when '&' then '&amp;'
111
+ when '"' then '&quot;'
112
+ when '{' then '&#123;'
113
+ when '}' then '&#125;'
114
+ when '[' then '&#91;'
115
+ when ']' then '&#93;'
116
+ when '*' then '&#42;'
117
+ when '#' then '&#35;'
118
+ when ':' then '&#58;'
119
+ when ';' then '&#59;'
120
+ when "'" then '&#39;'
121
+ when '=' then '&#61;'
122
+ else
123
+ c
124
+ end
125
+ return c
126
+ end
127
+
128
+ def elem
129
+ Builder::XmlMarkup.new
130
+ end
131
+
132
+ def element_name=(val)
133
+ @ename = val
134
+ end
135
+
136
+ def element_content=(val)
137
+ @econtent = val
138
+ end
139
+
140
+ def in_quotes?
141
+ @in_quotes || @in_single_quotes ? true : false
142
+ end
143
+
144
+ def new_char()
145
+ case
146
+ # tag name
147
+ when @start_tag == 1 && current_char == ' '
148
+ self.element_name = self.data.strip.downcase
149
+ self.data = ""
150
+ @start_tag = 2
151
+
152
+ # tag is closed <tag/> no attributes
153
+ when @start_tag == 1 && previous_char == '/' && current_char == '>'
154
+ self.data.chop!
155
+ self.element_name = self.data.strip.downcase
156
+ self.data = ""
157
+ @start_tag = 0
158
+ return false
159
+
160
+ # open tag
161
+ when @start_tag == 1 && previous_char != '/' && current_char == '>'
162
+ self.element_name = self.data.strip.downcase
163
+ self.data = ""
164
+ @start_tag = 0
165
+ return false if SHORT_TAGS.include?(self.element_name)
166
+ return false if self.element_name == @tag_check && NO_NEED_TO_CLOSE.include?(self.element_name)
167
+
168
+ # new tag attr
169
+ when @start_tag == 2 && current_char == ' ' && self.in_quotes? == false
170
+ self.current_param = self.data
171
+ self.data = ""
172
+ self.params << ""
173
+
174
+ # tag attribute name
175
+ when @start_tag == 2 && current_char == '=' && self.in_quotes? == false
176
+ self.current_param = self.data
177
+ self.data = ""
178
+ self.name_current_param()
179
+
180
+ # tag is now open
181
+ when @start_tag == 2 && previous_char != '/' && current_char == '>'
182
+ self.current_param = self.data
183
+ self.data = ""
184
+ @start_tag = 0
185
+ return false if SHORT_TAGS.include?(self.element_name)
186
+ return false if self.element_name == @tag_check && NO_NEED_TO_CLOSE.include?(self.element_name)
187
+
188
+ # tag is closed <example/>
189
+ when @start_tag == 2 && previous_char == '/' && current_char == '>'
190
+ self.current_param = self.data.chop
191
+ self.data = ""
192
+ @start_tag = 0
193
+ return false
194
+
195
+ # in quotes
196
+ when @start_tag == 2 && current_char == "'" && previous_char != '\\' && !@in_quotes
197
+ @in_single_quotes = !@in_single_quotes
198
+
199
+ # in quotes
200
+ when @start_tag == 2 && current_char == '"' && previous_char != '\\' && !@in_single_quotes
201
+ @in_quotes = !@in_quotes
202
+
203
+ # start of a closing tag
204
+ when @start_tag == 0 && previous_char == '<' && current_char == '/'
205
+ self.element_content += self.data.chop
206
+ self.data = ""
207
+ @start_tag = 5
208
+
209
+ when @start_tag == 5 && (current_char == '>' || current_char == ' ') && !self.data.blank?
210
+ self.data = self.data.strip.downcase
211
+ if self.data == self.element_name
212
+ self.data = ""
213
+ return false
214
+ else
215
+ if @tag_check == self.data && NO_NEED_TO_CLOSE.include?(self.element_name)
216
+ self.data = "</#{self.data}>"
217
+ return false
218
+ else
219
+ self.element_content += "&lt;/#{self.data}&gt;"
220
+ @start_tag = 0
221
+ self.data = ""
222
+ end
223
+ end
224
+
225
+ else
226
+ if @start_tag == 0 && ESCAPED_TAGS.include?(self.element_name)
227
+ self.data += self.escape_char(current_char)
228
+ else
229
+ self.data += current_char
230
+ end
231
+ end
232
+ return true
233
+ end
234
+
235
+ end
236
+
237
+ end
@@ -0,0 +1,70 @@
1
+ module WikiCloth
2
+
3
+ class WikiBuffer::Link < WikiBuffer
4
+
5
+ def initialize(data="",options={})
6
+ super(data,options)
7
+ @in_quotes = false
8
+ end
9
+
10
+ def internal_link
11
+ @internal_link ||= false
12
+ end
13
+
14
+ def to_s
15
+ link_handler = @options[:link_handler]
16
+ unless self.internal_link
17
+ return link_handler.external_link("#{params[0]}".strip, "#{params[1]}".strip)
18
+ else
19
+ case
20
+ when params[0] =~ /^:(.*)/
21
+ return link_handler.link_for(params[0],params[1])
22
+ when params[0] =~ /^\s*([a-zA-Z0-9-]+)\s*:(.*)$/
23
+ return link_handler.link_for_resource($1,$2,params[1..-1])
24
+ else
25
+ return link_handler.link_for(params[0],params[1])
26
+ end
27
+ end
28
+ end
29
+
30
+ protected
31
+ def internal_link=(val)
32
+ @internal_link = (val == true ? true : false)
33
+ end
34
+
35
+ def new_char()
36
+ case
37
+ # check if this link is internal or external
38
+ when previous_char.blank? && current_char == '['
39
+ self.internal_link = true
40
+
41
+ # Marks the beginning of another paramater for
42
+ # the current object
43
+ when current_char == '|' && self.internal_link == true && @in_quotes == false
44
+ self.current_param = self.data
45
+ self.data = ""
46
+ self.params << ""
47
+
48
+ # URL label
49
+ when current_char == ' ' && self.internal_link == false && params[1].nil? && !self.data.blank?
50
+ self.current_param = self.data
51
+ self.data = ""
52
+ self.params << ""
53
+
54
+ # end of link
55
+ when current_char == ']' && ((previous_char == ']' && self.internal_link == true) || self.internal_link == false) && @in_quotes == false
56
+ self.data.chop! if self.internal_link == true
57
+ self.current_param = self.data
58
+ self.data = ""
59
+ return false
60
+
61
+ else
62
+ self.data += current_char unless current_char == ' ' && self.data.blank?
63
+ end
64
+
65
+ return true
66
+ end
67
+
68
+ end
69
+
70
+ end
@@ -0,0 +1,159 @@
1
+ module WikiCloth
2
+
3
+ class WikiBuffer::Table < WikiBuffer
4
+
5
+ def initialize(data="",options={})
6
+ super(data,options)
7
+ self.buffer_type = "table"
8
+ @start_table = true
9
+ @start_row = false
10
+ @start_caption = false
11
+ @in_quotes = false
12
+ end
13
+
14
+ def table_caption
15
+ @caption ||= ""
16
+ return @caption.kind_of?(Hash) ? @caption[:value] : @caption
17
+ end
18
+
19
+ def table_caption_attributes
20
+ @caption.kind_of?(Hash) ? @caption[:style] : ""
21
+ end
22
+
23
+ def rows
24
+ @rows ||= [ [] ]
25
+ end
26
+
27
+ def to_s
28
+ row_count = 0
29
+ ret = "<table" + (params[0].blank? ? "" : " #{params[0].strip}") + ">"
30
+ ret += "<caption" + (self.table_caption_attributes.blank? ? "" : " #{table_caption_attributes.strip}") +
31
+ ">#{table_caption.strip}</caption>" unless self.table_caption.blank?
32
+ for row in rows
33
+ row_count += 1
34
+ ret += "<tr" + (params[row_count].nil? || params[row_count].blank? ? "" : " #{params[row_count].strip}") + ">"
35
+ for cell in row
36
+ cell_attributes = cell[:style].blank? ? "" : " #{cell[:style].strip}"
37
+ ret += "<#{cell[:type]}#{cell_attributes}>\n#{cell[:value].strip}\n</#{cell[:type]}>"
38
+ end
39
+ ret += "</tr>"
40
+ end
41
+ ret += "</table>"
42
+ end
43
+
44
+ protected
45
+ def rows=(val)
46
+ @rows = val
47
+ end
48
+
49
+ def table_caption_attributes=(val)
50
+ @caption = { :style => val, :value => self.table_caption } unless @caption.kind_of?(Hash)
51
+ @caption[:style] = val if @caption.kind_of?(Hash)
52
+ end
53
+
54
+ def table_caption=(val)
55
+ @caption = val unless @caption.kind_of?(Hash)
56
+ @caption[:value] = val if @caption.kind_of?(Hash)
57
+ end
58
+
59
+ def next_row()
60
+ self.params << ""
61
+ self.rows << []
62
+ end
63
+
64
+ def next_cell(type="td")
65
+ if self.rows[-1].size == 0
66
+ self.rows[-1] = [ { :type => type, :value => "", :style => "" } ]
67
+ else
68
+ self.rows[-1][-1][:value] = self.data
69
+ self.rows[-1] << { :type => type, :value => "", :style => "" }
70
+ end
71
+ end
72
+
73
+ def new_char()
74
+ if @check_cell_data == 1
75
+ case
76
+ when current_char != '|' && @start_caption == false && self.rows[-1][-1][:style].blank?
77
+ self.rows[-1][-1][:style] = self.data
78
+ self.data = ""
79
+ when current_char != '|' && @start_caption == true && self.table_caption_attributes.blank?
80
+ self.table_caption_attributes = self.data
81
+ self.data = ""
82
+ end
83
+ @check_cell_data = 0
84
+ end
85
+
86
+ case
87
+ # Next table cell in row (TD)
88
+ when current_char == "|" && (previous_char == "\n" || previous_char == "|") && @in_quotes == false
89
+ self.data.chop!
90
+ self.next_cell() unless self.data.blank? && previous_char == "|"
91
+ self.data = ""
92
+
93
+ # Next table cell in row (TH)
94
+ when current_char == "!" && (previous_char == "\n" || previous_char == "!") && @in_quotes == false
95
+ self.data.chop!
96
+ self.next_cell('th')
97
+ self.data = ""
98
+
99
+ # End of a table
100
+ when current_char == '}' && previous_char == '|'
101
+ self.data = ""
102
+ self.rows[-1].pop
103
+ return false
104
+
105
+ # Start table caption
106
+ when current_char == '+' && previous_char == '|' && @in_quotes == false
107
+ self.data = ""
108
+ self.rows[-1].pop
109
+ @start_caption = true
110
+
111
+ # Table cell might have attributes
112
+ when current_char == '|' && previous_char != "\n" && @in_quotes == false
113
+ @check_cell_data = 1
114
+
115
+ # End table caption
116
+ when current_char == "\n" && @start_caption == true && @in_quotes == false
117
+ @start_caption = false
118
+ self.table_caption = self.data
119
+ self.data = ""
120
+
121
+ # in quotes
122
+ when current_char == '"' && previous_char != '\\'
123
+ @in_quotes = !@in_quotes
124
+ self.data += '"'
125
+
126
+ # Table params
127
+ when current_char == "\n" && @start_table == true && @in_quotes == false
128
+ @start_table = false
129
+ unless self.data.blank?
130
+ self.current_param = self.data
131
+ self.params << ""
132
+ end
133
+ self.data = ""
134
+
135
+ # Table row params
136
+ when current_char == "\n" && @start_row == true && @in_quotes == false
137
+ @start_row = false
138
+ unless self.data.blank?
139
+ self.current_param = self.data
140
+ end
141
+ self.data = ""
142
+
143
+ # Start new table row
144
+ when current_char == '-' && previous_char == '|' && @in_quotes == false
145
+ self.data.chop!
146
+ self.rows[-1].pop
147
+ self.next_row()
148
+ @start_row = true
149
+
150
+ else
151
+ self.data += current_char
152
+ end
153
+
154
+ return true
155
+ end
156
+
157
+ end
158
+
159
+ end