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.
- data/HISTORY +12 -0
- data/Manifest.txt +15 -0
- data/README.txt +180 -0
- data/Rakefile +17 -0
- data/TODO +7 -0
- data/examples/markup.html +343 -0
- data/examples/markup.owl +300 -0
- data/examples/scribblify.rb +27 -0
- data/examples/template.rhtml +37 -0
- data/lib/owlscribble.rb +715 -0
- data/test/test_lists.html +108 -0
- data/test/test_lists.owl +46 -0
- data/test/test_owlscribble.rb +125 -0
- data/test/test_toc.html +123 -0
- data/test/test_toc.owl +59 -0
- metadata +70 -0
data/examples/markup.owl
ADDED
@@ -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 & that}}}, displaying as "this & that". Typing {{{&}}} will actually show "&".
|
@@ -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>
|
data/lib/owlscribble.rb
ADDED
@@ -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
|