owlscribble 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|