New translationd added.
[moodle.git] / lib / wiki.php
blob44b1fe9a7b6a5a61e0d28405ab8ee1a293b9299f
1 <?php
3 ///////////////////////////////////////////////////////////////////////////
4 // wiki.php - class for Wiki style formatting
5 //
6 // Transforms input string with Wiki style formatting into HTML
7 //
8 //
9 ///////////////////////////////////////////////////////////////////////////
10 // //
11 // NOTICE OF COPYRIGHT //
12 // //
13 // //
14 // Copyright (C) 2003 Howard Miller - GUIDE - University of Glasgow
15 // guide.gla.ac.uk
16 // //
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. //
21 // //
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: //
26 // //
27 // http://www.gnu.org/copyleft/gpl.html //
28 // //
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 );
35 // state defines
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
42 // list defines
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
49 class Wiki {
51 var $block_state;
52 var $list_state;
53 var $list_depth;
54 var $spelling_on;
55 var $list_backtrack;
56 var $output; // output buffer
58 function close_block( $state ) {
59 // provide appropriate closure for block according to state
61 // if in list close this first
62 $lclose = "";
63 if ($this->list_state != LIST_NONE) {
64 $lclose = $this->do_list( " ",true );
67 $sclose = "";
68 switch ($state) {
69 case STATE_PARAGRAPH:
70 $sclose = "</p>\n";
71 break;
72 case STATE_BLOCKQUOTE:
73 $sclose = "</blockquote>\n";
74 break;
75 case STATE_PREFORM:
76 $sclose = "</pre>\n";
77 break;
78 case STATE_NOTIKI:
79 $sclose = "</pre>\n";
80 break;
83 return $lclose . $sclose;
87 function do_replace( $line, $mark, $tag ) {
88 // do the regex thingy for things like bold, italic etc
89 // $mark is the magic character, and $tag the HTML tag to insert
91 // BODGE: replace inline $mark characters in places where we want them ignored
92 // they will be put back after main substitutue, stops problems with eg, and/or
93 $bodge = chr(1);
94 $line = eregi_replace( '([[:alnum:]])'.$mark.'([[:alnum:]])', '\\1'.$bodge.'\\2',$line );
96 $regex = '(^| |[(.,])'.$mark.'([^'.$mark.']*)'.$mark.'([^[:alnum:]]|$)';
97 $replace = '\\1<'.$tag.'>\\2</'.$tag.'>\\3';
98 $line = eregi_replace( $regex, $replace, $line );
100 // BODGE: back we go
101 $line = eregi_replace( $bodge, $mark, $line );
103 return $line;
106 function do_replace_sub( $line, $mark, $tag ) {
107 // do regex for subscript and superscript (slightly different)
108 // $mark is the magic character and $tag the HTML tag to insert
110 $regex = $mark.'([^'.$mark.']*)'.$mark;
111 $replace = '<'.$tag.'>\\1</'.$tag.'>';
112 return eregi_replace( $regex, $replace, $line );
115 function do_list( $line, $blank=false ) {
116 // handle line with list character on it
117 // if blank line implies drop to level 0
119 // get magic character and then delete it from the line if not blank
120 if ($blank) {
121 $listchar="";
122 $count = 0;
124 else {
125 $listchar = $line{0};
126 $count = strspn( $line, $listchar );
127 $line = eregi_replace( "^[".$listchar."]+ ", "", $line );
130 // find what sort of list this character represents
131 $list_tag = "";
132 $item_tag = "";
133 $list_style = LIST_NONE;
134 switch ($listchar) {
135 case '*':
136 $list_tag = "ul";
137 $item_tag = "li";
138 $list_style = LIST_UNORDERED;
139 break;
140 case '#':
141 $list_tag = "ol";
142 $item_tag = "li";
143 $list_style = LIST_ORDERED;
144 break;
145 case ';':
146 $list_tag = "dl";
147 $item_tag = "dd";
148 $list_style = LIST_DEFINITION;
149 break;
150 case ':':
151 $list_tag = "dl";
152 $item_tag = "dt";
153 $list_style = LIST_DEFINITION;
154 break;
157 // tag opening/closing regime now - fun bit :-)
158 $tags = "";
160 // if depth has reduced do number of closes to restore level
161 for ($i=$this->list_depth; $i>$count; $i-- ) {
162 $close_tag = array_pop( $this->list_backtrack );
163 $tags = $tags . $close_tag;
166 // if depth has increased do number of opens to balance
167 for ($i=$this->list_depth; $i<$count; $i++ ) {
168 array_push( $this->list_backtrack, "</$list_tag>" );
169 $tags = $tags . "<$list_tag>";
172 // ok, so list state is now same as style and depth same as count
173 $this->list_state = $list_style;
174 $this->list_depth = $count;
176 // apply formatting to remainder of line
177 $line = $this->line_replace( $line );
179 if ($blank) {
180 $newline = $tags;
182 else {
183 $newline = $tags . "<$item_tag>" . $line . "</$item_tag>";
186 return $newline;
189 function line_replace( $line ) {
190 // return line after various formatting replacements
191 // have been made - order is vital to stop them interfering with each other
193 if (IN_MOODLE==1) {
194 global $CFG;
197 // ---- (at least) means a <HR>
198 $line = eregi_replace( "^-{4}.*", "<div class=\"hr\"><hr /></div>", $line );
200 // is this a list line (starts with * # ; :)
201 if (eregi( "^([*]+|[#]+|[;]+|[:]+) ", $line )) {
202 $line = $this->do_list( $line );
205 // typographic conventions
206 $line = str_replace( "--", "&#8212;", $line );
207 $line = str_replace( " - ", " &#8211; ", $line );
208 $line = str_replace( "...", " &#8230; ", $line );
209 $line = str_replace( "(R)", "&#174;", $line );
210 $line = str_replace( "(r)", "&#174;", $line );
211 $line = str_replace( "(TM)", "&#8482;", $line );
212 $line = str_replace( "(tm)", "&#8482;", $line );
213 $line = str_replace( "(C)", "&#169;", $line );
214 // $line = str_replace( "(c)", "&#169;", $line );
215 $line = str_replace( "1/4", "&#188;", $line );
216 $line = str_replace( "1/2", "&#189;", $line );
217 $line = str_replace( "3/4", "&#190;", $line );
219 $line = eregi_replace( "([[:digit:]]+[[:space:]]*)x([[:space:]]*[[:digit:]]+)", "\\1&#215;\\2", $line ); // (digits) x (digits) - multiply
221 // do formatting tags
222 // NOTE: The / replacement *has* to be first, or it will screw the
223 // HTML tags that are added by the other ones
224 $line = $this->do_replace( $line, "/", "em" );
225 $line = $this->do_replace( $line, "\*", "strong" );
226 $line = $this->do_replace( $line, "\+", "ins" );
227 $line = $this->do_replace( $line, "-", "del" );
228 $line = $this->do_replace_sub( $line, "~", "sub" );
229 $line = $this->do_replace_sub( $line, "\^", "sup" );
230 $line = $this->do_replace( $line, "\"", "q" );
231 // $line = $this->do_replace( $line, "'", "q" );
232 $line = $this->do_replace( $line, "%", "code" );
233 $line = $this->do_replace( $line, "@", "cite" );
235 // convert urls into proper link with optional link text URL(text)
236 $line = eregi_replace("([[:space:]]|^)([[:alnum:]]+)://([^[:space:]]*)([[:alnum:]#?/&=])\(([^)]+)\)",
237 "\\1<A HREF=\"\\2://\\3\\4\" TARGET=\"newpage\">\\5</A>", $line);
238 $line = eregi_replace("([[:space:]])www\.([^[:space:]]*)([[:alnum:]#?/&=])\(([^)]+)\)",
239 "\\1<A HREF=\"http://www.\\2\\3\" TARGET=\"newpage\">\\5</A>", $line);
241 // make urls (with and without httpd) into proper links
242 $line = eregi_replace("([[:space:]]|^)([[:alnum:]]+)://([^[:space:]]*)([[:alnum:]#?/&=])",
243 "\\1<A HREF=\"\\2://\\3\\4\" TARGET=\"newpage\">\\2://\\3\\4</A>", $line);
244 $line = eregi_replace("([[:space:]])www\.([^[:space:]]*)([[:alnum:]#?/&=])",
245 "\\1<A HREF=\"http://www.\\2\\3\" TARGET=\"newpage\">www.\\2\\3</A>", $line);
247 // make email addresses into mailtos....
248 $line = eregi_replace("([[:space:]]|^)([a-zA-Z0-9@.]+)\(([^)]+)\)",
249 "\\1<a href=\"mailto:\\2\">\\3</a>", $line);
251 // !# at the beginning of any lines means a heading
252 $line = eregi_replace( "^!([1-6]) (.*)$", "<h\\1>\\2</h\\1>", $line );
254 // acronym handing, example HTML(Hypertext Markyp Language)
255 $line = ereg_replace( "([A-Z]+)\(([^)]+)\)", "<acronym title=\"\\2\">\\1</acronym>", $line );
257 // *Moodle Specific*
258 if (IN_MOODLE==1) {
259 // Replace resource link >>##(Description Text)
260 $line = eregi_replace( " ([a-zA-Z]+):([0-9]+)\(([^)]+)\)",
261 " <a href=\"".$CFG->wwwroot."/mod/\\1/view.php?id=\\2\">\\3</a> ", $line );
263 // Replace picture resource link
264 global $course; // This is a bit risky - it won't work everywhere
266 if ($CFG->slasharguments) {
267 $line = eregi_replace( "/([a-zA-Z0-9./_-]+)(png|gif|jpg)\(([^)]+)\)",
268 "<img src=\"$CFG->wwwroot/file.php/$course->id/\\1\\2\" alt=\"\\3\" />", $line );
269 } else {
270 $line = eregi_replace( "/([a-zA-Z0-9./_-]+)(png|gif|jpg)\(([^)]+)\)",
271 "<img src=\"$CFG->wwwroot/file.php\?file=$course->id/\\1\\2\" alt=\"\\3\" />", $line );
274 replace_smilies( $line );
278 return $line;
282 function spellcheck( $line,$pspell_link ) {
284 // split line into words
285 $words = preg_split( "/[\s,-.]/ ", $line );
287 // run through words
288 $newline = "";
289 foreach($words as $word) {
290 $check_word = eregi_replace( "[,;:./&()* ?\"]", "", $word );
291 $check_word = eregi_replace( "^'|'$","",$check_word );
293 // words not to check
294 $docheck = true;
295 if (eregi("[0-9]",$check_word)) { $docheck=false; }
297 if ( $docheck && (!pspell_check( $pspell_link, $check_word)) ) {
298 $suggests = pspell_suggest( $pspell_link,$check_word );
299 $suggest_line = "";
300 foreach($suggests as $suggest) {
301 $suggest_line = $suggest_line . " " . $suggest;
303 $word = "<span class=\"spellcheck\"><acronym title=\"$suggest_line\">$word</acronym></span>";
305 $newline = $newline . " " . $word;
308 return $newline;
312 function format( $content ) {
313 // main entry point for processing TikiText
314 // $content is string containing text with Tiki formatting
315 // return: string containing XHTML formatting
317 // initialisation stuff
318 $this->output = "";
319 $this->block_state = STATE_NONE;
320 $this->list_state = LIST_NONE;
321 $this->list_depth = 0;
322 $this->list_backtrack = array();
323 $this->spelling_on = false;
325 // split content into array of single lines
326 $lines = explode( "\n",$content );
327 $buffer = "";
329 // add a wiki div tag for CSS
330 $buffer = $buffer . "<div class=\"wiki\">\n";
332 // run through lines
333 foreach( $lines as $line ) {
335 // is this a blank line?
336 $blank_line = eregi( "^[[:blank:]\r]*$", $line );
337 if ($blank_line) {
338 // first end current block according to state
339 $buffer = $buffer . $this->close_block( $this->block_state );
340 $this->block_state = STATE_NONE;
341 continue;
344 // is this a spelling line
345 $spell_parms = array();
346 $spelling = eregi( "^!SPELL:([a-z]+):?(american|british|canadian)?(\r| |$)", $line,$spell_parms );
347 if ($spelling) {
348 $this->spelling_on = true;
349 $pspell_link = pspell_new( $spell_parms[1], $spell_parms[2] );
350 $line = "";
353 // spellcheck
354 if ($this->spelling_on) {
355 $line = $this->spellcheck( $line, $pspell_link );
358 // act now depending on current block state
359 if ($this->block_state == STATE_NONE) {
360 // first character of line defines block type
361 if (eregi( "^> ",$line )) {
362 // blockquote
363 $buffer = $buffer . "<blockquote>\n";
364 $buffer = $buffer . $this->line_replace( eregi_replace( "^>","",$line) ). "\n";
365 $this->block_state = STATE_BLOCKQUOTE;
367 else
368 if (eregi( "^ ",$line) ) {
369 // preformatted text
370 $buffer = $buffer . "<pre>\n";
371 $buffer = $buffer . $this->line_replace($line) . "\n";
372 $this->block_state = STATE_PREFORM;
374 else
375 if (eregi("^\% ",$line) ) {
376 // preformatted text - no processing
377 $buffer = $buffer . "<pre>\n";
378 $buffer = $buffer . eregi_replace( "^\%","",$line) . "\n";
379 $this->block_state = STATE_NOTIKI;
381 else
382 if (eregi("^Q\. ",$line) ) {
383 // Question - para with a question class
384 $buffer = $buffer . "<p class=\"question\">\n";
385 $buffer = $buffer . eregi_replace( "^Q. ","",$line) . "\n";
386 $this->block_state = STATE_PARAGRAPH;
388 else
389 if (eregi("^A\. ",$line) ) {
390 // Answer - para with an answer class
391 $buffer = $buffer . "<p class=\"answer\">\n";
392 $buffer = $buffer . eregi_replace( "^A. ","",$line ) . "\n";
393 $this->block_state = STATE_PARAGRAPH;
395 else {
396 // ordinary paragraph
397 $buffer = $buffer . "<p>\n";
398 $buffer = $buffer . $this->line_replace($line) . "\n";
399 $this->block_state = STATE_PARAGRAPH;
401 continue;
404 if (($this->block_state == STATE_PARAGRAPH) |
405 ($this->block_state == STATE_BLOCKQUOTE) |
406 ($this->block_state == STATE_PREFORM) ) {
407 $buffer = $buffer . $this->line_replace($line) . "\n";
408 continue;
410 elseif ($this->block_state == STATE_NOTIKI) {
411 $buffer = $buffer . $line . "\n";
415 // close off any block level tags
416 $buffer = $buffer . $this->close_block( $this->block_state );
418 // close off wiki div
419 $buffer = $buffer . "</div>\n";
421 //return $buffer;
422 return $buffer;