owlscribble 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,300 @@
1
+ = Table of Contents =
2
+ ##TableOfContents##
3
+
4
+ = Welcome to OWLScribble! =
5
+ //An introduction to the ~OWLScribble markup language.//
6
+
7
+ == What is OWLScribble? ==
8
+ ~OWLScribble is a wiki markup language //based on// the markup used by [hTTp://www.openwiki.org/ OpenWiki]. It was designed for use with the ~SewWiki project by GavinKistner.
9
+
10
+ == Supported Markup ==
11
+ === Paragraphs ===
12
+ {{{
13
+ Paragraphs start on a line of their own
14
+ and may be continued onto multiple
15
+ consecutive lines.
16
+
17
+ A double line break indicates the start
18
+ of a new paragraph.
19
+
20
+ If the start of the text is indented at all,
21
+ however, then it is treated as preformatted
22
+ text.
23
+ }}}
24
+
25
+ Paragraphs start on a line of their own
26
+ and may be continued onto multiple
27
+ consecutive lines.
28
+
29
+ A double line break indicates the start
30
+ of a new paragraph.
31
+
32
+ If the start of the text is indented at all,
33
+ however, then it is treated as preformatted
34
+ text.
35
+ ==== Indented Paragraphs ====
36
+ {{{
37
+ : An exception to the indented-paragraph rule is this.
38
+ : Paragraphs (on a single line)
39
+ : with whitespace preceding a colon
40
+ : are indented based on the amount of space
41
+ : But, as seen here, each line is its own paragraph.
42
+ }}}
43
+
44
+ : An exception to the indented-paragraph rule is this.
45
+ : Paragraphs (on a single line)
46
+ : with whitespace preceding a colon
47
+ : are indented based on the amount of space
48
+ : But, as seen here, each line is its own paragraph.
49
+
50
+ === Preformatted Text ===
51
+ {{{
52
+ {{{
53
+ for ( var i=0; i<10; ++i ){
54
+ alert( 'hi!' );
55
+ }
56
+ }}}
57
+ }}}
58
+
59
+ {{{
60
+ for ( var i=0; i<10; ++i ){
61
+ alert( 'hi!' );
62
+ }
63
+ }}}
64
+
65
+ To be clear about the start and end, you may
66
+ specify preformatted text by wrapping it in
67
+ @@{{{...}}}@@ over multiple lines.
68
+
69
+ Such text is automatically unindented by the
70
+ amount of indentation on the end markers.
71
+
72
+
73
+ === Basic Inline Styling ===
74
+ {{{
75
+ This text **is bold**, this //is italic//,
76
+ and --this has been struck--.
77
+
78
+ //You can start a paragraph with an inline
79
+ style, but you cannot wrap it across lines.//
80
+
81
+ This is a @@code reference@@,
82
+ as is {{{this text}}}.
83
+
84
+ A double-exclamation point is a special
85
+ 'todo' item. !!Add more examples!!
86
+
87
+ You can also ^^superscript^^ and
88
+ __subscript__ text, like H__2__O
89
+ or e^^pi*i^^.
90
+ }}}
91
+
92
+ This text **is bold**, this //is italic//,
93
+ and --this has been struck--.
94
+
95
+ //You can start a paragraph with an inline
96
+ style, but you cannot wrap it across lines.//
97
+
98
+ This is a @@code reference@@,
99
+ as is {{{this text}}}.
100
+
101
+ A double-exclamation point is a special
102
+ 'todo' item. !!Add more examples!!
103
+
104
+ You can also ^^superscript^^ and
105
+ __subscript__ text, like H__2__O
106
+ or e^^pi*i^^.
107
+
108
+ * //Unlike ~OpenWiki, you are not allowed to underline text. Sorry, but underlining is reserved for links in hyperlinked documents.//
109
+
110
+ === Headings ===
111
+ {{{
112
+ = Heading Level 1 =
113
+ == Heading Level 2 ==
114
+ === Heading Level 3 ===
115
+ ==== Heading Level 4 ====
116
+ ===== Heading Level 5 =====
117
+ ====== Heading Level 6 ======
118
+ }}}
119
+ Headings must begin at the start of the line. Markup inside headings is ignored. (For example,
120
+ {{{== My //Sweet// Heading ==}}} will not make the word italic, but will instead show the {{{//}}} characters in the output.)
121
+
122
+ === Linking ===
123
+ {{{
124
+ * Visit SiteMap, HomePage, or WhereDoIBegin to get started.
125
+ * Visit the [[What is a Wiki?]] page if you're really lost.
126
+ * Visit hTTp://www.google.com to find stuff on the web.
127
+ * The [hTTp://www.microsoft.com company that shall not be named] thinks they can beat Google.
128
+ * This page was edited by [GavinKistner the Wiki Gardener].
129
+ * See [[[Foo/Bar/Jim/Jam]] the Jam page] for more deliciousness.
130
+ }}}
131
+ * Visit SiteMap, HomePage, or WhereDoIBegin to get started.
132
+ * Visit the [[What is a Wiki?]] page if you're really lost.
133
+ * Visit hTTp://www.google.com to find stuff on the web.
134
+ * The [hTTp://www.microsoft.com company that shall not be named] thinks they can beat Google.
135
+ * This page was edited by [GavinKistner the Wiki Gardener].
136
+ * See [[[Foo/Bar/Jim/Jam]] the Jam page] for more deliciousness.
137
+
138
+ ~OWLScribble supports three styles of specifying a link:
139
+ 1. Text that looks like ~URLs are automatically linked. You must use a capital 'T' and lowercase letters in the protocol to get it recognized. (It's annoying, but designed to stop spam bots.) So http://www.google.com/ will not be linked, but {{{hTTp://www.google.com}}} will yield: hTTp://www.google.com
140
+ * //Supported protocols are http, https, and ftp.//
141
+ 2. **~WikiWords** are automatically treated as links to other pages in the Wiki, and expanded out. For example, typing {{{CapitalizedWordsScrunchedTogether}}} produces "CapitalizedWordsScrunchedTogether".
142
+ * //The ~OWLScribble class simply turns the above into the 'HTML' code: {{{<wiki_link page="CapitalizedWordsScrunchedTogether">Capitalized Words Scrunched Together</wiki_link>}}}. It is up to the consumer of ~OWLScribble to run through the {{{#wiki_links}}} collection and replace the above with something reasonable (such as an HTML anchor) after doing whatever it needs to do (such as a db lookup).//
143
+ 3. If you want to create a wiki page whose name doesn't look like a ~WikiWord, you can wrap it in double square brackets, like {{{See the [[Table of Contents]] page}}}.
144
+ * //Again, ~OWLScribble turns the above into: {{{<wiki_link page="Table of Contents">Table of Contents</wiki_link>}}}, leaving it up to the consumer of ~OWLScribble to change that tag into something better.//
145
+ * As seen in the last example above, you can use the double-square-bracket wiki link style along with outer brackets to supply different page text. ~OWLScribble turns the last example above into: {{{<wiki_link page="Foo/Bar/Jim/Jam">the Jam page</wiki_link>}}}.//
146
+
147
+ With either ~URLs or ~WikiWords, you can optionally use an alternative description for the link text. As shown above, place a single set of square brackets around what you want linked, with the URL or ~WikiWord as the first item.
148
+
149
+ ==== Preventing Linking ====
150
+ {{{
151
+ Sometimes you want to write something that looks like a
152
+ ~WikiWord (such as ~OpenWiki) but you don't want it to
153
+ appear as a link to a page in the wiki.
154
+
155
+ You can either wrap it in preformatted text delimiters
156
+ like @@{{{OpenWiki}}}@@, or place a tilde (~) character right
157
+ before it, as in {{{~OpenWiki}}}.
158
+ }}}
159
+
160
+ Sometimes you want to write something that looks like a
161
+ ~WikiWord (such as ~OpenWiki) but you don't want it to
162
+ appear as a link to a page in the wiki.
163
+
164
+ You can either wrap it in preformatted text delimiters
165
+ like @@{{{OpenWiki}}}@@, or place a tilde (~) character right
166
+ before it, as in {{{~OpenWiki}}}.
167
+
168
+
169
+ === Lists ===
170
+
171
+ ==== Bulleted Lists ====
172
+ {{{
173
+ 1 2 3 4 5 6
174
+ 12345678901234567890123456789012345678901234567890123456789012345
175
+ * Bullet lists need **at least one space** before the asterisk.
176
+ * **A space** must appear after the asterisk.
177
+ * They may be nested to an arbitrary depth.
178
+ * Tabs may also be used for indentation.
179
+ * Mixing tabs and spaces is asking for trouble.
180
+ }}}
181
+
182
+ * Bullet lists need **at least one space** before the asterisk.
183
+ * **A space** must appear after the asterisk.
184
+ * They may be nested to an arbitrary depth.
185
+ * Tabs may also be used for indentation.
186
+ * Mixing tabs and spaces is asking for trouble.
187
+
188
+ ==== Numbered Lists ====
189
+ {{{
190
+ 1 2 3 4 5 6
191
+ 12345678901234567890123456789012345678901234567890123456789012345
192
+ 1. Numbered lists must also have a space before the number.
193
+ 1. The actual number doesn't matter...
194
+ 3. ...but the period after the number does.
195
+ 1. As well as the space after the period.
196
+ # You can also use the 'octothorp' if you like.
197
+ # //(A fancy name for the 'sharp' or 'pound' symbol.)
198
+ # Other types of numbered lists are supported beyond arabic.
199
+ a. For arabic lists, you can use 1, 2, 3, ... 23, etc.
200
+ a. but for the following, you must use the actual character.
201
+ a. A lowercase 'a' is for lowercase alphabetical lists.
202
+ A. An uppercase 'A' is for uppercase alphabetical lists.
203
+ i. A lowercase 'i' is for lowercase roman numeral lists.
204
+ I. An uppercase 'I' is for uppercase roman numeral lists.
205
+ }}}
206
+
207
+ 1. Numbered lists must also have a space before the number.
208
+ 1. The actual number doesn't matter...
209
+ 3. ...but the period after the number does.
210
+ 1. As well as the space after the period.
211
+ # You can also use the 'octothorp' if you like.
212
+ # //(A fancy name for the 'sharp' or 'pound' symbol.)
213
+ # Other types of numbered lists are supported beyond arabic.
214
+ a. For arabic lists, you can use 1, 2, 3, ... 23, etc.
215
+ a. but for the following, you must use the actual character.
216
+ a. A lowercase 'a' is for lowercase alphabetical lists.
217
+ A. An uppercase 'A' is for uppercase alphabetical lists.
218
+ i. A lowercase 'i' is for lowercase roman numeral lists.
219
+ I. An uppercase 'I' is for uppercase roman numeral lists.
220
+
221
+
222
+ ==== Mixing Lists ====
223
+ {{{
224
+ 1 2 3 4 5
225
+ 12345678901234567890123456789012345678901234567890123456789
226
+ * Bulleted and numbered lists may be alternately nested
227
+ 1. And (as seen above)...
228
+ i. ...you can mix numbered list styles on the fly
229
+ * But woe unto you if you try to mix a bullet...
230
+ 1. ...and a number in the same list.
231
+ * //Sorry buddy, but that doesn't fly.//
232
+ }}}
233
+
234
+ * Bulleted and numbered lists may be alternately nested
235
+ 1. And (as seen above)...
236
+ i. ...you can mix numbered list styles on the fly
237
+ * But woe unto you if you try to mix a bullet...
238
+ 1. ...and a number in the same list.
239
+ * //Sorry buddy, but that doesn't fly.//
240
+
241
+
242
+ ==== Definition Lists ====
243
+ {{{
244
+ 1 2 3 4 5
245
+ 12345678901234567890123456789012345678901234567890123456789
246
+ ; Semantic : of or relating to meaning in language
247
+ ; Grok : to understand //completely//
248
+ ; : to inherently and almost intuitively comprehend
249
+ }}}
250
+
251
+ ; Semantic : of or relating to meaning in language
252
+ ; Grok : to understand //completely//
253
+ ; : to inherently and almost intuitively comprehend
254
+
255
+ * //Unlike ~OpenWiki, you cannot 'nest' or indent definition lists.//
256
+
257
+ === Tables ===
258
+ {{{
259
+ || **Age** || **Sex** || **Weight** ||
260
+ || 32 || M || 180 ||
261
+ || 30 || F || 150 ||
262
+ |||| //average// || **165** ||
263
+ }}}
264
+
265
+ || **Age** || **Sex** || **Weight** ||
266
+ || 32 || M || 180 ||
267
+ || 30 || F || 150 ||
268
+ |||| //average// || **165** ||
269
+
270
+ You don't have to line up the bars from one row to the next, but you may
271
+ if you want the text representation to look clean. As seen above,
272
+ you can cause a cell to span multiple columns.
273
+
274
+ == Processing Directives ==
275
+ A processing directive appears as a pair of pound symbols with a command
276
+ inside, optionally followed by parameters to that command inside
277
+ parentheses. For example:
278
+
279
+ {{{
280
+ ##TableOfContents##
281
+ ##IncludePage(ProcessingDirectives)##
282
+ }}}
283
+
284
+ ~OWLScribble currently recognizes the following directives:
285
+ * {{{##TableOfContents##}}} - replaces the directive with a nested list representing the hierarchy of headers in the document, with links to those headers
286
+
287
+ * {{{##DEPRECATED##}}} - indicates that the document is no longer needed, and should be removed
288
+
289
+ * {{{##BALETED##}}} - //same as DEPRECATED//
290
+
291
+ * {{{##IncludePage(PageName)##}}} - indicates that the contents of the other page should be included in this location
292
+
293
+ Note that ~OWLScribble only handles the {{{TableOfContents}}} directive natively. The other directives are replaced with an empty non-HTML 'wiki_command' tag, such as {{{<wiki_command do="TableOfContents" />}}} or {{{<wiki_command do="IncludePage" param="CommonFooter" />}}}. It is up to the consumer of ~OWLScribble to handle and/or replace such tags with meaningful information.
294
+
295
+ : //There are two ways to handle these tags. One way is to parse and modify the HTML produced by ~OWLScribble as a string. This is effective, but not recommended.//
296
+ : //The recommended mechanism for handling processing directives is to spin through the {{{#wiki_commands}}} collection of the ~OWLScribble object after initialization, processing the commands and replacing the tags as desired. Once this is done, call the {{{#to_s}}} or {{{#to_html}}} methods on the ~OWLScribble instance to get the final output.//
297
+
298
+
299
+ == Miscellaneous ==
300
+ HTML entities are not needed (and do not work) inside ~OWLScribble; you can type {{{"this & that"}}} and it will produce the HTML {{{this &amp; that}}}, displaying as "this & that". Typing {{{&amp;}}} will actually show "&amp;".
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+ require 'owlscribble'
4
+
5
+ file_name = ARGV[0] || 'markup.owl'
6
+
7
+ require 'cgi'
8
+ OWLScribble.each_wiki_link{ |tag, page_name, link_text|
9
+ tag.name = 'a'
10
+ tag.href = "dummylinktopage?#{CGI.escape(page_name)}"
11
+ tag.class = "newwikilink"
12
+ }
13
+
14
+ markup = IO.read( file_name )
15
+ owl = OWLScribble.new( markup )
16
+ p owl if $DEBUG
17
+
18
+ html = owl.to_html
19
+
20
+ require 'erb'
21
+ File.open( file_name.gsub( /[^.]+$/, 'html' ), "w+" ){ |out|
22
+ File.open( 'template.rhtml', 'r' ){ |template|
23
+ page_title = nil
24
+ page_contents = html
25
+ out << ERB.new( template.read ).result( binding )
26
+ }
27
+ }
@@ -0,0 +1,37 @@
1
+ <html><head><title><%=page_title || 'Welcome to the Jungle'%></title>
2
+ <style type="text/css">
3
+ body { margin:1em 2em; font-size:90%; font-family:'Trebuchet MS', serif; margin-bottom:30em }
4
+
5
+ a:link, a:visited { color:#009 }
6
+ a.newwikilink { color:#900 }
7
+
8
+ h1,h2,h3,h4,h5,h6 { margin-bottom:0.2em; margin-top:2em; border-bottom:1px solid #999; font-family:sans-serif }
9
+ h1 { font-size:120% }
10
+ h2 { font-size:110% }
11
+ h3,h4 { font-size:100% }
12
+ h5,h6 { font-size:90% }
13
+ h4,h5,h6 { font-style:italic }
14
+
15
+ ul, ol { margin:0; padding:0; margin-left:1.5em; margin-bottom:0.8em }
16
+ ul ol, ol ul, ul ul, ol ol { margin-left:1.5em }
17
+ li { margin-bottom:0 }
18
+ p { margin:0; margin-bottom:0.7em }
19
+ pre { background:#ffd; padding:0.5em; border:1px solid #993 }
20
+ .todo { font-weight:bold; color:#900 }
21
+ .section { margin-left:1em }
22
+ table { border-collapse:collapse; margin-bottom:1em }
23
+ td { border:1px solid #ccc; padding:0.1em 0.5em; font-size:85% }
24
+ pre, code, tt { color:#060 }
25
+
26
+ dt { font-weight:bold }
27
+
28
+ .indent1 { margin-left:2em }
29
+ .indent2 { margin-left:4em }
30
+ .indent3 { margin-left:6em }
31
+ .indent4 { margin-left:8em }
32
+ .indent5 { margin-left:10em }
33
+ .indent6 { margin-left:12em }
34
+ </style>
35
+ </head><body>
36
+ <%=page_contents%>
37
+ </body></html>
@@ -0,0 +1,715 @@
1
+ #--
2
+ # *** This code is copyright 2005 by Gavin Kistner
3
+ # *** It is covered under the license viewable at http://phrogz.net/JS/_ReuseLicense.txt
4
+ # *** Reuse or modification is free provided you abide by the terms of that license.
5
+ # *** (Including the first two lines above in your source code usually satisfies the conditions.)
6
+ #++
7
+ # This file covers the OWLScribble class, and extensions to the String class needed by it.
8
+ # Please see the documentation on those classes for more information.
9
+ #
10
+ # See the link:../examples/markup.html file for details on the OWLScribble markup.
11
+
12
+ require 'rubygems'
13
+ gem 'tagtreescanner', '>=0.8'
14
+ require 'tagtreescanner'
15
+
16
+ # OWLScribble converts a specific set of text markup into HTML.
17
+ # (The syntax used in the markup is a knockoff of the markup used by
18
+ # OpenWiki, so the 'OWL' in OWLScribble means "OpenWiki-like".)
19
+ #
20
+ # See the README.txt file for more information.
21
+ #
22
+ # For details on the markup used by OWLScribble, please see the link:../examples/markup.html file.
23
+ class OWLScribble < TagTreeScanner
24
+ VERSION = "0.9.0"
25
+
26
+ attr_reader :list_items #:nodoc:
27
+ attr_reader :headers #:nodoc:
28
+
29
+ # An array of <tt><wiki_command></tt> tags representing directives to the wiki.
30
+ # <i>(See "Wiki Commands" above.)</i>
31
+ attr_reader :wiki_commands
32
+
33
+ # An array of <tt><wiki_link></tt> tags repesenting links to wiki pages.
34
+ # <i>(See "Wiki Links" above.)</i>
35
+ attr_reader :wiki_links
36
+
37
+ # A nested unordered list representing the hierarchy of the document, with links to the sections.
38
+ # <i>(See "Table of Contents" above.)</i>
39
+ attr_reader :table_of_contents
40
+
41
+ @root_factory = TagFactory.new( :root,
42
+ :allowed_genre => :block,
43
+ :allows_text => false )
44
+
45
+ # Letters and numbers cannot be mixed with whitespace due to http:// as opener.
46
+ @text_match = /[a-z0-9]+|[ \t]+|./mi
47
+
48
+ @tag_genres[ :block ] = [ ]
49
+ @tag_genres[ :table ] = [ ]
50
+ @tag_genres[ :td ] = [ ]
51
+ @tag_genres[ :deflist ] = [ ]
52
+ @tag_genres[ :inline ] = [ ]
53
+
54
+ # == My Heading == -> <h2>My Heading</h2>
55
+ @tag_genres[ :block ] << TagFactory.new( :heading,
56
+ :open_match => /(={1,6}) +(.+) +\1[ \t]*\n/, :open_requires_bol => true,
57
+ :setup => lambda{ |tag, ss, owl|
58
+ depth = ss[1].length
59
+ tag.name = "h#{depth}"
60
+ tag.info[ :header_depth ] = depth
61
+ tag << ss[2]
62
+ owl.headers << tag
63
+ },
64
+ :allows_text => true,
65
+ :autoclose => true
66
+ )
67
+
68
+ # * This is a bullet -> <li>This is a bullet</li>
69
+ # (The private #reparent_lists method adds <ul>...</ul>)
70
+ @tag_genres[ :block ] << TagFactory.new( :li,
71
+ :open_match => /([ \t]+)\* /, :open_requires_bol => true,
72
+ :close_match => /\n/,
73
+ :setup => lambda{ |tag, ss, owl|
74
+ tag.info[ :level ] = ss[1].length
75
+ tag.info[ :list_type ] = :ul
76
+ owl.list_items << tag
77
+ },
78
+ :allows_text => true,
79
+ :allowed_genre => :inline
80
+ )
81
+
82
+ # # Make functional -> <li>Make functional</li>
83
+ # (The private #reparent_lists method adds <ol>...</ol>)
84
+ @tag_genres[ :block ] << TagFactory.new( :li,
85
+ :open_match => /([ \t]+)# /, :open_requires_bol => true,
86
+ :close_match => /\n/,
87
+ :setup => lambda{ |tag, ss, owl|
88
+ tag.info[ :level ] = ss[1].length
89
+ tag.info[ :list_type ] = :ol
90
+ owl.list_items << tag
91
+ },
92
+ :allows_text => true,
93
+ :allowed_genre => :inline
94
+ )
95
+
96
+ # 2. Add good comments -> <li>Add good comments</li>
97
+ # (The private #reparent_lists method adds <ol>...</ol>)
98
+ @tag_genres[ :block ] << TagFactory.new( :li,
99
+ :open_match => /([ \t]+)\d+\. /, :open_requires_bol => true,
100
+ :close_match => /\n/,
101
+ :setup => lambda{ |tag, ss, owl|
102
+ tag.info[ :level ] = ss[1].length
103
+ tag.info[ :list_type ] = :ol
104
+ owl.list_items << tag
105
+ },
106
+ :allows_text => true,
107
+ :allowed_genre => :inline
108
+ )
109
+
110
+ # a. alpha 1 -> <li type="a">alpha 1</li>
111
+ # A. ALPHA 2 -> <li type="A">ALPHA 2</li>
112
+ # i. roman 3 -> <li type="i">roman 3</li>
113
+ # I. ROMAN 4 -> <li type="I">ROMAN 4</li>
114
+ # (The private #reparent_lists method adds <ol>...</ol>)
115
+ @tag_genres[ :block ] << TagFactory.new( :li,
116
+ :open_match => /([ \t]+)([aAiI])\. /, :open_requires_bol => true,
117
+ :close_match => /\n/,
118
+ :setup => lambda{ |tag, ss, owl|
119
+ tag.attributes[ :type ] = ss[2]
120
+ tag.info[ :level ] = ss[1].length
121
+ tag.info[ :list_type ] = :ol
122
+ owl.list_items << tag
123
+ },
124
+ :allows_text => true,
125
+ :allowed_genre => :inline
126
+ )
127
+
128
+ # ; Semantic : relating to meaning -> <dl><dt>Semantic</dt><dd>relating to meaning</dd></dl>
129
+ # ( requires the :dt and :dd matches in the :deflist genre )
130
+ @tag_genres[ :block ] << TagFactory.new( :dl,
131
+ :open_match => /(?=[ \t]+; .+ : )/, :open_requires_bol => true,
132
+ :close_match => /(?![ \t]+; .+ : )/, :close_requires_bol => true,
133
+ :allowed_genre => :deflist
134
+ )
135
+
136
+ # {{{
137
+ # This is a multi-line
138
+ # code block
139
+ # }}}
140
+ # -> <pre>This is a multi-line
141
+ # code block</pre>
142
+ @tag_genres[ :block ] << TagFactory.new( :pre,
143
+ :open_match => /^([ \t]*)\{\{\{\n(.+?)\n\1\}\}\}\n/m, :open_requires_bol => true,
144
+ :close_match => /\n/,
145
+ :setup => lambda{ |tag, ss, owl| tag << ss[2].gsub( /^#{ss[1]}/, '' ) },
146
+ :allows_text => true,
147
+ :autoclose => true
148
+ )
149
+
150
+ # || Age || Sex ||
151
+ # || 32 || M ||
152
+ # -> <table><tr><td>Age</td><td>Sex</td></tr><tr><td>32</td><td>M</td></tr></table>
153
+ # ( requires the :tr factory in the :table genre, and the :td factory in the :td genre )
154
+ @tag_genres[ :block ] << TagFactory.new( :table,
155
+ :open_match => /(?=\|\|)/, :open_requires_bol => true,
156
+ :close_match => /(?=[^|])/, :close_requires_bol => true,
157
+ :allowed_genre => :table
158
+ )
159
+
160
+ # : Indented Text! -> <p class="indent2">Indented Text!</p>
161
+ @tag_genres[ :block ] << TagFactory.new( :p,
162
+ :open_match => /(( )+) ?: /, :open_requires_bol => true,
163
+ :close_match => /\n/,
164
+ :setup => lambda{ |tag, ss, owl| tag.attributes[ :class ] = "indent#{ss[1].length/2}" },
165
+ :allows_text => true,
166
+ :allowed_genre => :inline
167
+ )
168
+
169
+ # ##TableOfContents## -> <wiki_command do="TableOfContents" />
170
+ @tag_genres[ :block ] << TagFactory.new( :wiki_command,
171
+ :open_match => /##TableOfContents##/, :open_requires_bol => true,
172
+ :setup => lambda{ |tag, ss, owl|
173
+ tag.attributes[ :do ] = "TableOfContents"
174
+ owl.wiki_commands << tag
175
+ },
176
+ :autoclose => true
177
+ )
178
+
179
+ # ##SomeCommand## or ##Command2( param1, param2 )##
180
+ @tag_genres[ :block ] << TagFactory.new( :wiki_command,
181
+ :open_match => /##(\w+)(?:\s*\(\s*(.+?)\s*\))?##/, :open_requires_bol => true,
182
+ :setup => lambda{ |tag, ss, owl|
183
+ command = ss[1]
184
+ params = ss[2] ? ss[2].split( /,\s*/ ) : []
185
+ owl.process_wiki_command( tag, command, params )
186
+ owl.wiki_commands << tag
187
+ },
188
+ :allows_text => true,
189
+ :autoclose => true
190
+ )
191
+
192
+ # ---- -> <hr />
193
+ @tag_genres[ :block ] << TagFactory.new( :hr,
194
+ :open_match => /-{4,} *\n/, :open_requires_bol => true,
195
+ :autoclose => true
196
+ )
197
+
198
+ # All other lines that begin with non-whitespace start a paragraph
199
+ @tag_genres[ :block ] << TagFactory.new( :p,
200
+ :open_match => /(?=\S)/, :open_requires_bol => true,
201
+ # Stop the paragraph for a double-newline or a line that looks like a list or header start
202
+ :close_match => /\n(?:\n|(?=[ \t]+(?:[*aiAI#]|\d+\.) )|(?=(={1,6}) +.+ +\1[ \t]*\n))/,
203
+ :allows_text => true,
204
+ :allowed_genre => :inline
205
+ )
206
+
207
+ # All other lines that begin with whitespace and have non-whitespace
208
+ # content later start a preformatted block
209
+ @tag_genres[ :block ] << TagFactory.new( :pre,
210
+ :open_match => /([ \t]+)([^\n]*\S.+?)^(?=\S)/m, :open_requires_bol => true,
211
+ :setup => lambda{ |tag, ss, owl|
212
+ tag << ss[2].gsub( /^#{ss[1]}/, '' )
213
+ },
214
+ :allows_text => true,
215
+ :autoclose => true
216
+ )
217
+
218
+
219
+ # (see the :table factory in :block genre)
220
+ @tag_genres[ :table ] << TagFactory.new( :tr,
221
+ :open_match => /(?=\|\|)/, :open_requires_bol => true,
222
+ :close_match => /\|\|[ \t]*\n/,
223
+ :allowed_genre => :td
224
+ )
225
+
226
+
227
+ # (see the :table factory in :block genre)
228
+ @tag_genres[ :td ] << TagFactory.new( :td,
229
+ :open_match => /((?:\|\|)+)\s*/,
230
+ :close_match => /(?=\s*\|\|)/,
231
+ :setup => lambda{ |tag, ss, owl|
232
+ colspan = ss[1].length / 2
233
+ tag.attributes[ :colspan ] = colspan unless colspan < 2
234
+ },
235
+ :allows_text => true,
236
+ :allowed_genre => :inline
237
+ )
238
+
239
+
240
+ # (see the :dl factory in :block genre)
241
+ @tag_genres[ :deflist ] << TagFactory.new( :dd,
242
+ :open_match => /[ \t]+;\s+: /, :open_requires_bol => true,
243
+ :close_match => /\n/,
244
+ :allows_text => true,
245
+ :allowed_genre => :inline
246
+ )
247
+ @tag_genres[ :deflist ] << TagFactory.new( :dt,
248
+ :open_match => /[ \t]+; /, :open_requires_bol => true,
249
+ :close_match => / : /,
250
+ :allows_text => true,
251
+ :allowed_genre => :inline
252
+ )
253
+ @tag_genres[ :deflist ] << TagFactory.new( :dd,
254
+ :open_match => /(?=.)/,
255
+ :close_match => /\n/,
256
+ :allows_text => true,
257
+ :allowed_genre => :inline
258
+ )
259
+
260
+
261
+ # This is **bold text** -> This is <b>bold text</b>
262
+ @tag_genres[ :inline ] << TagFactory.new( :b,
263
+ # Close up if we see a newline to prevent a typo from propagating too far
264
+ :open_match => /\*\*/, :close_match => /\*\*|(?=\n)/,
265
+ :allows_text => true,
266
+ :allowed_genre => :inline
267
+ )
268
+
269
+ # This is //italic text// -> This is <i>italic text</i>
270
+ @tag_genres[ :inline ] << TagFactory.new( :i,
271
+ # Close up if we see a newline to prevent a typo from propagating too far
272
+ :open_match => /\/\//, :close_match => /\/\/|(?=\n)/,
273
+ :allows_text => true,
274
+ :allowed_genre => :inline
275
+ )
276
+
277
+ # This is @@literal text@@ -> This is <tt>literal text</tt>
278
+ @tag_genres[ :inline ] << TagFactory.new( :tt,
279
+ :open_match => /@@([^\n]+?)@@/,
280
+ :setup => lambda{ |tag, ss, owl| tag << ss[1] },
281
+ :allows_text => true,
282
+ :autoclose => true
283
+ )
284
+
285
+ # This is {{{also literal text}}} -> This is <tt>also literal text</tt>
286
+ @tag_genres[ :inline ] << TagFactory.new( :tt,
287
+ :open_match => /\{\{\{([^\n]+?)\}\}\}/,
288
+ :setup => lambda{ |tag, ss, owl| tag << ss[1] },
289
+ :allows_text => true,
290
+ :autoclose => true
291
+ )
292
+
293
+ # hTTp://www.google.com -> <a href="hTTp://www.google.com">www.google.com</a>
294
+ # Require capital 'T' as a lame way to fight spammers
295
+ @tag_genres[ :inline ] << TagFactory.new( :a,
296
+ :open_match => /(?:hTTp|fTp|hTTps):\/\/(\S+)/,
297
+ :setup => lambda{ |tag, ss, owl|
298
+ tag.attributes[ :href ] = ss[0]
299
+ tag << ss[1]
300
+ },
301
+ :allows_text => true,
302
+ :autoclose => true
303
+ )
304
+
305
+ # http://www.google.com -> http://www.google.com
306
+ # ensure that double-slash from non-link doesn't start italics
307
+ @tag_genres[ :inline ] << TagFactory.new( :span,
308
+ :open_match => /(?:http|ftp|https):\/\/(\S+)/i,
309
+ :setup => lambda{ |tag, ss, owl|
310
+ tag << ss[0]
311
+ },
312
+ :allows_text => true,
313
+ :autoclose => true
314
+ )
315
+
316
+ # [hTTp://www.google.com Google is cool!] -> <a href="hTTp://www.google.com">Google is cool!</a>
317
+ # Require capital 'T' as a lame way to fight spammers
318
+ @tag_genres[ :inline ] << TagFactory.new( :a,
319
+ :open_match => /\[((?:hTTp|fTp|hTTps):\/\/\S+) +([^\]]+)\]/,
320
+ :setup => lambda{ |tag, ss, owl|
321
+ tag.attributes[ :href ] = ss[1]
322
+ tag << ss[2]
323
+ },
324
+ :allows_text => true,
325
+ :autoclose => true
326
+ )
327
+
328
+ # See the WhatWeShouldDo page -> See the <wiki_link page="WhatWeShouldDo">What We Should Do</wiki_link> page
329
+ # OWLScribble#wiki_links gives you a collection; use Tag#replace_with to swap in what you want before #to_html.
330
+ @tag_genres[ :inline ] << TagFactory.new( :wiki_link,
331
+ :open_match => /[A-Z]{2,}[a-z][a-zA-Z]*|[A-Z][a-z]+[A-Z][a-zA-Z]*/,
332
+ :setup => lambda{ |tag, ss, owl|
333
+ owl.process_wiki_link( tag, ss[0], ss[0].dewikiword )
334
+ owl.wiki_links << tag
335
+ },
336
+ :allows_text => true,
337
+ :autoclose => true
338
+ )
339
+
340
+ # See [[[Crazy! Go Nuts!]] this cool page] -> See the <wiki_link page="Crazy! Go Nuts!">this cool page</wiki_link> page
341
+ # OWLScribble#wiki_links gives you a collection; use Tag#replace_with to swap in what you want before #to_html.
342
+ @tag_genres[ :inline ] << TagFactory.new( :wiki_link,
343
+ :open_match => /\[\[\[(.{2,}?)\]\] ([^\]]+)\]/,
344
+ :setup => lambda{ |tag, ss, owl|
345
+ owl.process_wiki_link( tag, ss[1], ss[2].dewikiword )
346
+ owl.wiki_links << tag
347
+ },
348
+ :allows_text => true,
349
+ :autoclose => true
350
+ )
351
+
352
+ # See the [[Crazy! Go Nuts!]] page -> See the <wiki_link page="Crazy! Go Nuts!">Crazy! Go Nuts!</wiki_link> page
353
+ # OWLScribble#wiki_links gives you a collection; use Tag#replace_with to swap in what you want before #to_html.
354
+ @tag_genres[ :inline ] << TagFactory.new( :wiki_link,
355
+ :open_match => /\[\[(.{2,}?)\]\]/,
356
+ :setup => lambda{ |tag, ss, owl|
357
+ owl.process_wiki_link( tag, ss[1], ss[1] )
358
+ owl.wiki_links << tag
359
+ },
360
+ :allows_text => true,
361
+ :autoclose => true
362
+ )
363
+
364
+ # See the [WikiInstructions help page] for more -> See the <wiki_link page="WikiInstructions">help page</wiki_link> for more
365
+ @tag_genres[ :inline ] << TagFactory.new( :wiki_link,
366
+ :open_match => /\[([A-Z]{2,}[a-z][a-zA-Z]*|[A-Z][a-z]+[A-Z][a-zA-Z]*) ([^\]]+)\]/,
367
+ :setup => lambda{ |tag, ss, owl|
368
+ owl.process_wiki_link( tag, ss[1], ss[2] )
369
+ owl.wiki_links << tag
370
+ },
371
+ :allows_text => true,
372
+ :autoclose => true
373
+ )
374
+
375
+ # ~NotALink -> <span>NotALink</span>
376
+ @tag_genres[ :inline ] << TagFactory.new( :span,
377
+ :open_match => /~([A-Z]{2,}[a-z][a-zA-Z]*|[A-Z][a-z]+[A-Z][a-zA-Z]*|\[\[([^\]\n]{2,}?)\]\]|\[(?:[A-Z]{2,}[a-z][a-zA-Z]*|[A-Z][a-z]+[A-Z][a-zA-Z]*) [^\]]+\])/,
378
+ :setup => lambda{ |tag, ss, owl| tag << ss[1] },
379
+ :allows_text => true,
380
+ :autoclose => true
381
+ )
382
+
383
+ # This is --strike text-- -> This is <strike>strike text</strike>
384
+ @tag_genres[ :inline ] << TagFactory.new( :strike,
385
+ # Close up if we see a newline to prevent a typo from propagating too far
386
+ :open_match => /--/, :close_match => /--|(?=\n)/,
387
+ :allows_text => true,
388
+ :allowed_genre => :inline
389
+ )
390
+
391
+ # We should... !!finish this sentence!! -> We should... <span class="todo">TODO - finish this sentence</span>
392
+ @tag_genres[ :inline ] << TagFactory.new( :span,
393
+ :open_match => /!!([a-z].+?)!!/i,
394
+ :setup => lambda{ |tag, ss, owl|
395
+ tag << "TODO - #{ss[1]}"
396
+ tag.attributes[ :class ] = 'todo'
397
+ },
398
+ :allows_text => true,
399
+ :autoclose => true
400
+ )
401
+
402
+ # This is ^^superscript text^^ -> This is <sup>superscript text</sup>
403
+ @tag_genres[ :inline ] << TagFactory.new( :sup,
404
+ # Close up if we see a newline to prevent a typo from propagating too far
405
+ :open_match => /\^\^/, :close_match => /\^\^|(?=\n)/,
406
+ :allows_text => true,
407
+ :allowed_genre => :inline
408
+ )
409
+
410
+ # This is __subscript text__ -> This is <sub>subscript text</sub>
411
+ @tag_genres[ :inline ] << TagFactory.new( :sub,
412
+ # Close up if we see a newline to prevent a typo from propagating too far
413
+ :open_match => /__/, :close_match => /__|(?=\n)/,
414
+ :allows_text => true,
415
+ :allowed_genre => :inline
416
+ )
417
+
418
+ # Default handler; typically overridden by user using OWLScribble.each_wiki_link
419
+ @handle_wiki_link = proc{ |tag, page_name, link_text|
420
+ tag.name = 'wiki_link'
421
+ tag.text = link_text
422
+ tag.attributes[ :page ] = page_name
423
+ tag.attributes[ :class ] = 'wiki_link'
424
+ }
425
+
426
+ # Default handler; typically overridden by user using OWLScribble.each_wiki_command
427
+ @handle_wiki_command = proc{ |tag, command, params|
428
+ tag.name = 'wiki_command'
429
+ tag.attributes[ :do ] = command
430
+ if params && !params.empty?
431
+ tag.attributes[ :params ] = params.join(',')
432
+ tag.text = "###{command}( #{params.join ', '} )##"
433
+ else
434
+ tag.text = "###{command}##"
435
+ end
436
+ }
437
+
438
+ # RDoc thinks that this stuff applies to instances, not the class
439
+ # :stopdoc:
440
+ class << self
441
+ attr_accessor :handle_wiki_link, :handle_wiki_command
442
+ end
443
+ # :startdoc:
444
+
445
+ # Define how to handle each wiki link instead of using the default handler.
446
+ # The block you supply will be passed a TagTreeScanner::Tag instance,
447
+ # the name of the wiki page to link to, and the text to display for the link.
448
+ #
449
+ # The Tag defaults to <tt><wiki_link>link text</wiki_link></tt>, without the page name.
450
+ #
451
+ # Example usage:
452
+ # OWLScribble.each_wiki_link do |tag, page_name, link_text|
453
+ # if my_application.page_exists?( page_name )
454
+ # tag.name = "a" #=> html anchor
455
+ # tag.href = "page/view/#{CGI.escape(page_name)}"
456
+ # tag.title = "View #{page_name.dewikiword}"
457
+ # # tag.text is set to the page_name.dewikiword already
458
+ #
459
+ # elsif my_application.user_can_create_pages?
460
+ # tag.name = "a" #=> html anchor
461
+ # tag.href = "page/create/#{CGI.escape(page_name)}"
462
+ # tag.title = "Create #{page_name.dewikiword}"
463
+ # tag.text = page_name # no dewikiword
464
+ #
465
+ # else
466
+ # tag.name = "span"
467
+ # tag.class = "missing_page"
468
+ # tag.text = page_name # no dewikiword
469
+ # end
470
+ # end
471
+ def self.each_wiki_link( &block )
472
+ @handle_wiki_link = block
473
+ end
474
+
475
+ # Define how to handle each wiki command instead of using the default handler.
476
+ # The block you supply will be passed a TagTreeScanner::Tag instance,
477
+ # the name of the command, and an array of zero or more parameter strings.
478
+ #
479
+ # The Tag defaults to <tt><wiki_command>###command( param1, param2 )##</wiki_command></tt>.
480
+ #
481
+ # Example usage:
482
+ # OWLScribble.each_wiki_command do |tag, command, params|
483
+ # case command
484
+ #
485
+ # # Format is Include( page_name )
486
+ # # or Include( page_name, revision )
487
+ # when 'Include'
488
+ # tag.name = 'div'
489
+ # tag.class = 'sub_page'
490
+ # page_name, revision = params
491
+ # # All elements in the params array are strings
492
+ # revision = Integer( revision ) rescue nil
493
+ # tag.text = fetch_page( page_name, revision )
494
+ #
495
+ # when 'TitleIndex'
496
+ # # ...set tag.html here
497
+ #
498
+ # else
499
+ # tag.name = 'span'
500
+ # tag.class = 'unhandled_command'
501
+ # tag.text = "###{command}( #{params.join ', '} )##"
502
+ # end
503
+ # end
504
+ def self.each_wiki_command( &block )
505
+ @handle_wiki_command = block
506
+ end
507
+
508
+ # _owl_string_ is the raw OWLScribble markup to convert.
509
+ #
510
+ # You likely want to use OWLScribble.each_wiki_link and (possibly) OWLScribble.each_wiki_command
511
+ # to define how to produce wiki-specific markup, prior to creating OWLScribble instances.
512
+ def initialize( owl_string )
513
+ @list_items = []
514
+ @headers = []
515
+ @wiki_commands = []
516
+ @wiki_links = []
517
+
518
+ super
519
+
520
+ reparent_lists( @list_items ) unless @list_items.empty?
521
+ #TODO - process indented paragraphs like lists, just to calculate minimal depth from many spaces
522
+ add_sections
523
+
524
+ # Must do this after add_sections has been called
525
+ @wiki_commands.each{ |command_tag|
526
+ case command_tag.attributes[ :do ]
527
+ when 'TableOfContents'
528
+ command_tag.replace_with( @table_of_contents.dup )
529
+ end
530
+ }
531
+ end
532
+
533
+ def process_wiki_link( tag, page_name, link_text ) #:nodoc:
534
+ tag.text = link_text
535
+ self.class.handle_wiki_link[ tag, page_name, link_text ]
536
+ end
537
+
538
+ # This is not called for ##TableOfContents##, since we know
539
+ # how to handle that natively, and it must be handled after
540
+ # the full document hierarchy is created.
541
+ def process_wiki_command( tag, command, params ) #:nodoc:
542
+ if !params || params.empty?
543
+ tag.text = "###{command}##"
544
+ else
545
+ tag.text = "###{command}( #{params.join(', ')} )##"
546
+ end
547
+ self.class.handle_wiki_command[ tag, command, params ]
548
+ end
549
+
550
+ private
551
+ # Wrap <ul> and <ol> around tags appropriately
552
+ # Extra work required to handle malformed nesting reasonably
553
+ def reparent_lists( list_items )
554
+ # Gather information about the tags before we munge the tree
555
+ list_items.each_with_index{ |tag, i|
556
+ tag.info[ :index ] = i
557
+ tag.info[ :new_level ] = !tag.previous_sibling || ( tag.previous_sibling.name != :li )
558
+ }
559
+
560
+ factories = {
561
+ :ul => TagFactory.new( :ul ),
562
+ :ol => TagFactory.new( :ol )
563
+ }
564
+
565
+ last_item, seen_at_level = nil
566
+ list_items.dup.each{ |item|
567
+ # cached for (premature) speed optimization
568
+ info = item.info
569
+
570
+ # reset history if we're in a new list
571
+ if info[ :new_level ]
572
+ seen_at_level = [ ]
573
+ last_item = nil
574
+ end
575
+
576
+ # cached for (premature) speed optimization
577
+ level = info[ :level ]
578
+
579
+ # factory to create the <ul> or <ol>
580
+ factory = factories[ item.info[ :list_type ] ]
581
+
582
+ # add as child of last item, if I'm any deeper
583
+ if last_item && level > last_item.info[ :level ]
584
+ group_parent = last_item.append_child( factory.create )
585
+ last_item.info[ :child_grouper ] = group_parent
586
+
587
+ # at same level or higher than previous item
588
+ else
589
+ # sibling or parents (my level or higher)
590
+ relatives = seen_at_level[ 1..level ]
591
+
592
+ # find the most recent sibling/parent
593
+ if relatives && ( parent_or_sib = relatives.compact.sort_by{ |tag| tag.info[ :index ] }.last )
594
+
595
+ # same level as me? It's a sibling!
596
+ if parent_or_sib.info[ :level ] == level
597
+ group_parent = parent_or_sib.parent_tag
598
+
599
+ # must be higher, thus it'll be my parent
600
+ else
601
+ group_parent = parent_or_sib.info[ :child_grouper ]
602
+ end
603
+
604
+ # I'm higher than anyone else (first item or malformed input)
605
+ else
606
+ group_parent = item.parent_tag.insert_before( factory.create, item )
607
+ end
608
+
609
+ end
610
+ group_parent.append_child( item )
611
+
612
+ seen_at_level[ level ] = item
613
+ last_level = level
614
+ last_item = item
615
+ }
616
+ end
617
+
618
+ # Wrap <div class="section">...</div> around all headers
619
+ # and create the table of contents. Called automatically during initialization.
620
+ def add_sections( )
621
+ @table_of_contents = TagFactory.new( :div, :attributes => { :class => 'toc' } ).create
622
+
623
+ section_factory = TagFactory.new( :div, :attributes => { :class => 'section' } )
624
+
625
+ current_section = nil
626
+ last_header = nil
627
+ head_count = 0
628
+ header_titles = Hash.new( 0 )
629
+ header_at_depth = []
630
+ relatives = nil
631
+
632
+ # dup the array so that changes to the hierarchy
633
+ # don't screw up the iteration
634
+ @root.child_tags.dup.each{ |tag|
635
+ # We assume that only headers have 'header_depth' information
636
+ # because we're too lazy to test for h1-h6 name
637
+ if depth = tag.info[ :header_depth ]
638
+ tag.info[ :head_count ] = head_count += 1
639
+
640
+ # Create a TOC entry for this header
641
+ header_title = tag.inner_text.gsub( /\W/, '' )
642
+ header_id = header_title = tag.inner_text.gsub( /\W/, '' )
643
+ hits = ( header_titles[ header_title ] += 1 )
644
+ header_id += hits.to_s if hits > 1
645
+ #header_id = "heading_#{head_count}"
646
+ tag.attributes[ :id ] = header_id
647
+ toc_item = @table_of_contents.append_child( Tag.new( :li ) )
648
+ toc_item.info = { :level => depth, :list_type => :ul }
649
+ anchor = toc_item.append_child( tag.dup )
650
+ anchor.name = :a
651
+ anchor.attributes = { :href => "##{header_id}" }
652
+ @table_of_contents.append_child( toc_item )
653
+
654
+ # The wrapper div corresponding to this header
655
+ section = tag.info[ :section ] = section_factory.create
656
+
657
+ # if I'm any deeper than the last header, I'm a direct child
658
+ if last_header && depth > last_header.info[ :header_depth ]
659
+ section_parent = current_section
660
+
661
+ # See if I can find a previous sibling or parent (my level or above)
662
+ elsif ( relatives = header_at_depth[ 1..depth ] ) &&
663
+ ( parent_or_sib = relatives.compact.sort_by{ |header| header.info[ :head_count ] }.last )
664
+
665
+ if parent_or_sib.info[ :header_depth ] == depth
666
+ # same level as me? It's a sibling!
667
+ section_parent = parent_or_sib.parent_tag
668
+ else
669
+ # must be higher, thus its section will be my parent
670
+ section_parent = parent_or_sib.info[ :section ]
671
+ end
672
+
673
+ # I've got no basis for attachment, go to the 'top' level
674
+ else
675
+ section_parent = tag.parent_tag
676
+ end
677
+ section_parent.append_child( tag )
678
+ section_parent.append_child( section )
679
+
680
+ header_at_depth[ depth ] = tag
681
+ current_section = section
682
+ last_header = tag
683
+
684
+ # we didn't find a header, so just shove the current
685
+ # tag into the desired depth
686
+ elsif current_section
687
+ current_section.append_child( tag )
688
+ end
689
+ }
690
+
691
+ reparent_lists( @table_of_contents.child_tags )
692
+ end
693
+ end
694
+
695
+ # Extensions to the built-in String class
696
+ class String
697
+ # Returns a copy of the string with spaces inserted in the 'appropriate'
698
+ # spots. e.g.
699
+ #
700
+ # "WikiWord".dewikiword #-> "Wiki Word"
701
+ # "OWLScribble".dewikiword #-> "OWL Scribble"
702
+ # "ToXML".dewikiword #-> "To XML"
703
+ # "KingOfWhales".dewikiword #-> "King of Whales"
704
+ def dewikiword
705
+ self.dup.dewikiword!
706
+ end
707
+
708
+ # Same as #dewikiword, but modifies the string in place
709
+ def dewikiword!
710
+ gsub! /([A-Z][a-z]+)(?=[A-Z])/, '\1 \2'
711
+ gsub! /([A-Z])([A-Z][a-z])/, '\1 \2'
712
+ gsub!( /\b(?:A|Am|An|And|Are|Is|Of|The|With)\b/ ){ |s| s.downcase }
713
+ self
714
+ end
715
+ end