owlscribble 0.9.0

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.
@@ -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