3 ///////////////////////////////////////////////////////////////////////////
4 // wiki.php - class for Wiki style formatting
6 // Transforms input string with Wiki style formatting into HTML
9 ///////////////////////////////////////////////////////////////////////////
11 // NOTICE OF COPYRIGHT //
14 // Copyright (C) 2003 Howard Miller - GUIDE - University of Glasgow
17 // This program is free software; you can redistribute it and/or modify //
18 // it under the terms of the GNU General Public License as published by //
19 // the Free Software Foundation; either version 2 of the License, or //
20 // (at your option) any later version. //
22 // This program is distributed in the hope that it will be useful, //
23 // but WITHOUT ANY WARRANTY; without even the implied warranty of //
24 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
25 // GNU General Public License for more details: //
27 // http://www.gnu.org/copyleft/gpl.html //
29 ///////////////////////////////////////////////////////////////////////////
31 // set this to 1 *IF* we are running in Moodle
32 // it enables Moodle specific functions, otherwise 0
33 define( "IN_MOODLE",1 );
36 define( "STATE_NONE",1 ); // blank line has been detected, so looking for first line on next para
37 define( "STATE_PARAGRAPH",2 ); // currently processing vanilla paragraph
38 define( "STATE_BLOCKQUOTE",3 ); // currently processing blockquote section
39 define( "STATE_PREFORM",4 ); // currently processing preformatted text
40 define( "STATE_NOTIKI",5 ); // currently processing preformatted / no formatting
43 define( "LIST_NONE", 1 ); // no lists active
44 define( "LIST_UNORDERED", 2 ); // unordered list active
45 define( "LIST_ORDERED", 3 ); // ordered list active
46 define( "LIST_DEFINITION", 4 ); // definition list active
55 var $output; // output buffer
57 function close_block( $state ) {
58 // provide appropriate closure for block according to state
60 // if in list close this first
62 if ($this->list_state
!= LIST_NONE
) {
63 $lclose = $this->do_list( " ",true );
71 case STATE_BLOCKQUOTE
:
72 $sclose = "</blockquote>\n";
82 return $lclose . $sclose;
86 function do_replace( $line, $mark, $tag ) {
87 // do the regex thingy for things like bold, italic etc
88 // $mark is the magic character, and $tag the HTML tag to insert
90 // BODGE: replace inline $mark characters in places where we want them ignored
91 // they will be put back after main substitutue, stops problems with eg, and/or
93 $line = eregi_replace( '([[:alnum:]])'.$mark.'([[:alnum:]])', '\\1'.$bodge.'\\2',$line );
95 $regex = '(^| |[(.,])'.$mark.'([^'.$mark.']*)'.$mark.'([^[:alnum:]])';
96 $replace = '\\1<'.$tag.'>\\2</'.$tag.'>\\3';
97 $line = eregi_replace( $regex, $replace, $line );
100 $line = eregi_replace( $bodge, $mark, $line );
105 function do_replace_sub( $line, $mark, $tag ) {
106 // do regex for subscript and superscript (slightly different)
107 // $mark is the magic character and $tag the HTML tag to insert
109 $regex = $mark.'([^'.$mark.']*)'.$mark;
110 $replace = '<'.$tag.'>\\1</'.$tag.'>';
111 return eregi_replace( $regex, $replace, $line );
114 function do_list( $line, $blank=false ) {
115 // handle line with list character on it
116 // if blank line implies drop to level 0
118 // get magic character and then delete it from the line if not blank
124 $listchar = $line{0};
125 $count = strspn( $line, $listchar );
126 $line = eregi_replace( "^[".$listchar."]+ ", "", $line );
129 // find what sort of list this character represents
132 $list_style = LIST_NONE
;
137 $list_style = LIST_UNORDERED
;
142 $list_style = LIST_ORDERED
;
147 $list_style = LIST_DEFINITION
;
152 $list_style = LIST_DEFINITION
;
156 // tag opening/closing regime now - fun bit :-)
159 // if depth has reduced do number of closes to restore level
160 for ($i=$this->list_depth
; $i>$count; $i-- ) {
161 $close_tag = array_pop( $this->list_backtrack
);
162 $tags = $tags . $close_tag;
165 // if depth has increased do number of opens to balance
166 for ($i=$this->list_depth
; $i<$count; $i++
) {
167 array_push( $this->list_backtrack
, "</$list_tag>" );
168 $tags = $tags . "<$list_tag>";
171 // ok, so list state is now same as style and depth same as count
172 $this->list_state
= $list_style;
173 $this->list_depth
= $count;
175 // apply formatting to remainder of line
176 $line = $this->line_replace( $line );
182 $newline = $tags . "<$item_tag>" . $line . "</$item_tag>";
188 function line_replace( $line ) {
189 // return line after various formatting replacements
190 // have been made - order is vital to stop them interfering with each other
196 // ---- (at least) means a <HR>
197 $line = eregi_replace( "^-{4}.*", "<div class=\"hr\"><hr /></div>", $line );
199 // is this a list line (starts with * # ; :)
200 if (eregi( "^([*]+|[#]+|[;]+|[:]+) ", $line )) {
201 $line = $this->do_list( $line );
204 // typographic conventions
205 $line = str_replace( "--", "—", $line );
206 $line = str_replace( " - ", " – ", $line );
207 $line = str_replace( "...", " … ", $line );
208 $line = str_replace( "(R)", "®", $line );
209 $line = str_replace( "(r)", "®", $line );
210 $line = str_replace( "(TM)", "™", $line );
211 $line = str_replace( "(tm)", "™", $line );
212 $line = str_replace( "(C)", "©", $line );
213 $line = str_replace( "(c)", "©", $line );
214 $line = str_replace( "1/4", "¼", $line );
215 $line = str_replace( "1/2", "½", $line );
216 $line = str_replace( "3/4", "¾", $line );
218 $line = eregi_replace( "([[:digit:]]+[[:space:]]*)x([[:space:]]*[[:digit:]]+)", "\\1×\\2", $line ); // (digits) x (digits) - multiply
220 // do formatting tags
221 // NOTE: The / replacement *has* to be first, or it will screw the
222 // HTML tags that are added by the other ones
223 $line = $this->do_replace( $line, "/", "em" );
224 $line = $this->do_replace( $line, "\*", "strong" );
225 $line = $this->do_replace( $line, "\+", "ins" );
226 $line = $this->do_replace( $line, "-", "del" );
227 $line = $this->do_replace_sub( $line, "~", "sub" );
228 $line = $this->do_replace_sub( $line, "\^", "sup" );
229 $line = $this->do_replace( $line, "\"", "q" );
230 $line = $this->do_replace( $line, "'", "q" );
231 $line = $this->do_replace( $line, "%", "code" );
232 $line = $this->do_replace( $line, "@", "cite" );
234 // convert urls into proper link with optional link text URL(text)
235 $line = eregi_replace("([[:space:]]|^)([[:alnum:]]+)://([^[:space:]]*)([[:alnum:]#?/&=])\(([^)]+)\)",
236 "\\1<A HREF=\"\\2://\\3\\4\" TARGET=\"newpage\">\\5</A>", $line);
237 $line = eregi_replace("([[:space:]])www\.([^[:space:]]*)([[:alnum:]#?/&=])\(([^)]+)\)",
238 "\\1<A HREF=\"http://www.\\2\\3\" TARGET=\"newpage\">\\5</A>", $line);
240 // make urls (with and without httpd) into proper links
241 $line = eregi_replace("([[:space:]]|^)([[:alnum:]]+)://([^[:space:]]*)([[:alnum:]#?/&=])",
242 "\\1<A HREF=\"\\2://\\3\\4\" TARGET=\"newpage\">\\2://\\3\\4</A>", $line);
243 $line = eregi_replace("([[:space:]])www\.([^[:space:]]*)([[:alnum:]#?/&=])",
244 "\\1<A HREF=\"http://www.\\2\\3\" TARGET=\"newpage\">www.\\2\\3</A>", $line);
246 // !# at the beginning of any lines means a heading
247 $line = eregi_replace( "^!([1-6]) (.*)$", "<h\\1>\\2</h\\1>", $line );
249 // acronym handing, example HTML(Hypertext Markyp Language)
250 $line = ereg_replace( "([A-Z]+)\(([^)]+)\)", "<acronym title=\"\\2\">\\1</acronym>", $line );
254 // Replace resource link >>##(Description Text)
255 $line = eregi_replace( " ([a-zA-Z]+):([0-9]+)\(([^)]+)\)",
256 " <a href=\"".$CFG->wwwroot
."/mod/\\1/view.php?id=\\2\">\\3</a> ", $line );
258 // Replace picture resource link
259 global $course; // This is a bit risky - it won't work everywhere
261 if ($CFG->slasharguments
) {
262 $line = eregi_replace( "/([a-zA-Z./_-]+)(png|gif|jpg)\(([^)]+)\)",
263 "<img src=\"$CFG->wwwroot/file.php/$course->id/\\1\\2\" alt=\"\\3\" />", $line );
265 $line = eregi_replace( "/([a-zA-Z./_-]+)(png|gif|jpg)\(([^)]+)\)",
266 "<img src=\"$CFG->wwwroot/file.php\?file=$course->id/\\1\\2\" alt=\"\\3\" />", $line );
269 replace_smilies( $line );
276 function format( $content ) {
277 // main entry point for processing TikiText
278 // $content is string containing text with Tiki formatting
279 // return: string containing XHTML formatting
281 // initialisation stuff
283 $this->block_state
= STATE_NONE
;
284 $this->list_state
= LIST_NONE
;
285 $this->list_depth
= 0;
286 $this->list_backtrack
= array();
288 // split content into array of single lines
289 $lines = explode( "\n",$content );
292 // add a wiki div tag for CSS
293 $buffer = $buffer . "<div class=\"wiki\">\n";
296 foreach( $lines as $line ) {
298 // is this a blank line?
299 $blank_line = eregi( "^[[:blank:]\r]*$", $line );
301 // first end current block according to state
302 $buffer = $buffer . $this->close_block( $this->block_state
);
303 $this->block_state
= STATE_NONE
;
307 // act now depending on current block state
308 if ($this->block_state
== STATE_NONE
) {
309 // first character of line defines block type
310 if (eregi( "^> ",$line )) {
312 $buffer = $buffer . "<blockquote>\n";
313 $buffer = $buffer . $this->line_replace( eregi_replace( "^>","",$line) ). "\n";
314 $this->block_state
= STATE_BLOCKQUOTE
;
317 if (eregi( "^ ",$line) ) {
319 $buffer = $buffer . "<pre>\n";
320 $buffer = $buffer . $this->line_replace($line) . "\n";
321 $this->block_state
= STATE_PREFORM
;
324 if (eregi("^\% ",$line) ) {
325 // preformatted text - no processing
326 $buffer = $buffer . "<pre>\n";
327 $buffer = $buffer . eregi_replace( "^\%","",$line) . "\n";
328 $this->block_state
= STATE_NOTIKI
;
331 if (eregi("^Q. ",$line) ) {
332 // Question - para with a question class
333 $buffer = $buffer . "<p class=\"question\">\n";
334 $buffer = $buffer . eregi_replace( "^Q. ","",$line) . "\n";
335 $this->block_state
= STATE_PARAGRAPH
;
338 if (eregi("^A. ",$line) ) {
339 // Answer - para with an answer class
340 $buffer = $buffer . "<p class=\"answer\">\n";
341 $buffer = $buffer . eregi_replace( "^A. ","",$line ) . "\n";
342 $this->block_state
= STATE_PARAGRAPH
;
345 // ordinary paragraph
346 $buffer = $buffer . "<p>\n";
347 $buffer = $buffer . $this->line_replace($line) . "\n";
348 $this->block_state
= STATE_PARAGRAPH
;
353 if (($this->block_state
== STATE_PARAGRAPH
) |
354 ($this->block_state
== STATE_BLOCKQUOTE
) |
355 ($this->block_state
== STATE_PREFORM
) ) {
356 $buffer = $buffer . $this->line_replace($line) . "\n";
359 elseif ($this->block_state
== STATE_NOTIKI
) {
360 $buffer = $buffer . $line . "\n";
364 // close off any block level tags
365 $buffer = $buffer . $this->close_block( $this->block_state
);
367 // close off wiki div
368 $buffer = $buffer . "</div>\n";