]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/transform.php
log
[SourceForge/phpwiki.git] / lib / transform.php
1 <?php rcs_id('$Id: transform.php,v 1.15 2001-02-12 01:43:10 dairiki Exp $');
2
3 define('WT_TOKENIZER', 1);
4 define('WT_SIMPLE_MARKUP', 2);
5 define('WT_MODE_MARKUP', 3);
6
7 define("ZERO_LEVEL", 0);
8 define("NESTED_LEVEL", 1);
9
10 class WikiTransform
11 {
12    /*
13    function WikiTransform() -- init
14
15    function register($type, $function)
16         Registers transformer functions
17         This should be done *before* calling do_transform
18
19         $type ... one of WT_TOKENIZER, WT_SIMPLE_MARKUP, WT_MODE_MARKUP
20                   Currently on WT_MODE_MARKUP has a special meaning.
21                   If one WT_MODE_MARKUP really sets the html mode, then
22                   all successive WT_MODE_MARKUP functions are skipped
23
24         $function ... function name
25
26    function SetHTMLMode($tag, $tagtype, $level)
27         Wiki HTML output can, at any given time, be in only one mode.
28         It will be something like Unordered List, Preformatted Text,
29         plain text etc. When we change modes we have to issue close tags
30         for one mode and start tags for another.
31         SetHTMLMode takes care of this.
32
33         $tag ... HTML tag to insert
34         $tagtype ... ZERO_LEVEL - close all open tags before inserting $tag
35                      NESTED_LEVEL - close tags until depths match
36         $level ... nesting level (depth) of $tag
37                    nesting is arbitrary limited to 10 levels
38
39    function do_transform($html, $content)
40         contains main-loop and calls transformer functions
41
42         $html ... HTML header (if needed, otherwise '')
43         $content ... wiki markup as array of lines
44    */
45
46
47    // public variables (only meaningful during do_transform)
48    var $linenumber;     // current linenumber
49    var $replacements;   // storage for tokenized strings of current line
50    var $tokencounter;   // counter of $replacements array
51
52    // private variables
53    var $content;        // wiki markup, array of lines
54    var $mode_set;       // stores if a HTML mode for this line has been set
55    var $trfrm_func;     // array of registered functions
56    var $stack;          // stack for SetHTMLMode (keeping track of open tags)
57
58    // init function
59    function WikiTransform()
60    {
61       $this->trfrm_func = array();
62       $this->stack = new Stack;
63    }
64
65    // register transformation functions
66    function register($type, $function)
67    {
68       $this->trfrm_func[] = array ($type, $function);
69    }
70    
71    // sets current mode like list, preformatted text, plain text, ...
72    // takes care of closing (open) tags
73    function SetHTMLMode($tag, $tagtype, $level)
74    {
75       $this->mode_set = 1;      // in order to prevent other mode markup
76                                 // to be executed
77       $retvar = '';
78
79       if ($tagtype == ZERO_LEVEL) {
80          // empty the stack until $level == 0;
81          if ($tag == $this->stack->top()) {
82             return; // same tag? -> nothing to do
83          }
84          while ($this->stack->cnt() > 0) {
85             $closetag = $this->stack->pop();
86             $retvar .= "</$closetag>\n";
87          }
88    
89          if ($tag) {
90             $retvar .= "<$tag>\n";
91             $this->stack->push($tag);
92          }
93
94
95       } elseif ($tagtype == NESTED_LEVEL) {
96          if ($level <= $this->stack->cnt()) {
97             // $tag has fewer nestings (old: tabs) than stack,
98             // reduce stack to that tab count
99             while ($this->stack->cnt() > $level) {
100                $closetag = $this->stack->pop();
101                if ($closetag == false) {
102                   //echo "bounds error in tag stack";
103                   break;
104                }
105                $retvar .= "</$closetag>\n";
106             }
107
108             // if list type isn't the same,
109             // back up one more and push new tag
110             if ($tag != $this->stack->top()) {
111                $closetag = $this->stack->pop();
112                $retvar .= "</$closetag><$tag>\n";
113                $this->stack->push($tag);
114             }
115    
116          } else { // $level > $this->stack->cnt()
117             // we add the diff to the stack
118             // stack might be zero
119             while ($this->stack->cnt() < $level) {
120                $retvar .= "<$tag>\n";
121                $this->stack->push($tag);
122                if ($this->stack->cnt() > 10) {
123                   // arbitrarily limit tag nesting
124                   ExitWiki(gettext ("Stack bounds exceeded in SetHTMLOutputMode"));
125                }
126             }
127          }
128
129       } else { // unknown $tagtype
130          ExitWiki ("Passed bad tag type value in SetHTMLOutputMode");
131       }
132
133       return $retvar;
134    }
135    // end SetHTMLMode
136
137
138    // work horse and main loop
139    // this function does the transform from wiki markup to HTML
140    function do_transform($html, $content)
141    {
142       global $FieldSeparator;
143
144       $this->content = $content;
145
146       // Loop over all lines of the page and apply transformation rules
147       $numlines = count($this->content);
148       for ($lnum = 0; $lnum < $numlines; $lnum++)
149       {
150          $this->tokencounter = 0;
151          $this->replacements = array();
152          $this->linenumber = $lnum;
153          $line = $this->content[$lnum];
154
155          // blank lines clear the current mode
156          if (!strlen($line) || $line == "\r") {
157             $html .= $this->SetHTMLMode('', ZERO_LEVEL, 0);
158             continue;
159          }
160
161          $this->mode_set = 0;
162
163          // main loop applying all registered functions
164          // tokenizers, markup, html mode, ...
165          // functions are executed in order of registering
166          for ($func = 0; $func < count($this->trfrm_func); $func++) {
167             // if HTMLmode is already set then skip all following
168             // WT_MODE_MARKUP functions
169             if ($this->mode_set &&
170                ($this->trfrm_func[$func][0] == WT_MODE_MARKUP)) {
171                continue;
172             }
173             // call registered function
174             $line = $this->trfrm_func[$func][1]($line, $this);
175          }
176
177          // Replace tokens ($replacements was filled by wtt_* functions)
178          for ($i = 0; $i < $this->tokencounter; $i++) {
179              $line = str_replace($FieldSeparator.$FieldSeparator.$i.$FieldSeparator, $this->replacements[$i], $line);
180          }
181
182          $html .= $line . "\n";
183       }
184       // close all tags
185       $html .= $this->SetHTMLMode('', ZERO_LEVEL, 0);
186
187       return $html;
188    }
189    // end do_transfrom()
190
191 }
192 // end class WikiTransform
193
194
195    //////////////////////////////////////////////////////////
196
197    $transform = new WikiTransform;
198
199    // register functions
200    // functions are applied in order of registering
201
202    $transform->register(WT_TOKENIZER, 'wtt_bracketlinks');
203    $transform->register(WT_TOKENIZER, 'wtt_urls');
204    if (function_exists('wtt_interwikilinks')) {
205       $transform->register(WT_TOKENIZER, 'wtt_interwikilinks');
206    }
207    $transform->register(WT_TOKENIZER, 'wtt_bumpylinks');
208
209    $transform->register(WT_SIMPLE_MARKUP, 'wtm_htmlchars');
210    $transform->register(WT_SIMPLE_MARKUP, 'wtm_linebreak');
211    $transform->register(WT_SIMPLE_MARKUP, 'wtm_bold_italics');
212    $transform->register(WT_SIMPLE_MARKUP, 'wtm_title_search');
213    $transform->register(WT_SIMPLE_MARKUP, 'wtm_fulltext_search');
214    $transform->register(WT_SIMPLE_MARKUP, 'wtm_mostpopular');
215
216    $transform->register(WT_MODE_MARKUP, 'wtm_list_ul');
217    $transform->register(WT_MODE_MARKUP, 'wtm_list_ol');
218    $transform->register(WT_MODE_MARKUP, 'wtm_list_dl');
219    $transform->register(WT_MODE_MARKUP, 'wtm_preformatted');
220    $transform->register(WT_MODE_MARKUP, 'wtm_headings');
221    $transform->register(WT_MODE_MARKUP, 'wtm_hr');
222    $transform->register(WT_MODE_MARKUP, 'wtm_paragraph');
223
224    $html = $transform->do_transform($html, $pagehash['content']);
225
226
227 /*
228 Requirements for functions registered to WikiTransform:
229
230 Signature:  function wtm_xxxx($line, &$transform)
231
232 $line ... current line containing wiki markup
233         (Note: it may already contain HTML from other transform functions)
234 &$transform ... WikiTransform object -- public variables of this
235         object and their use see above.
236
237 Functions have to return $line (doesn't matter if modified or not)
238 All conversion should take place inside $line.
239
240 Tokenizer functions should use $transform->replacements to store
241 the replacement strings. Also, they have to keep track of
242 $transform->tokencounter. See functions below. Back substitution
243 of tokenized strings is done by do_transform().
244 */
245
246
247
248    //////////////////////////////////////////////////////////
249    // Tokenizer functions
250
251    // helper function which does actual tokenizing and is
252    // called by other wtt_* functions
253    function wt_tokenize($str, $pattern, &$orig, &$ntokens) {
254       global $FieldSeparator;
255       // Find any strings in $str that match $pattern and
256       // store them in $orig, replacing them with tokens
257       // starting at number $ntokens - returns tokenized string
258       $new = '';      
259       while (preg_match("/^(.*?)($pattern)/", $str, $matches)) {
260          $linktoken = $FieldSeparator . $FieldSeparator . ($ntokens++) . $FieldSeparator;
261          $new .= $matches[1] . $linktoken;
262          $orig[] = $matches[2];
263          $str = substr($str, strlen($matches[0]));
264       }
265       $new .= $str;
266       return $new;
267    }
268
269
270    // New linking scheme: links are in brackets. This will
271    // emulate typical HTML linking as well as Wiki linking.
272    function wtt_bracketlinks($line, &$trfrm)
273    {
274       static $footnotes = array();
275
276       // protecting [[
277       $n = $ntok = $trfrm->tokencounter;
278       $line = wt_tokenize($line, '\[\[', $trfrm->replacements, $ntok);
279       while ($n < $ntok) {
280          $trfrm->replacements[$n++] = '[';
281       }
282
283       // match anything else between brackets 
284       $line = wt_tokenize($line, '\[.+?\]', $trfrm->replacements, $ntok);
285       while ($n < $ntok) {
286         $link = ParseAndLink($trfrm->replacements[$n]);
287         if (strpos($link['type'], 'footnote') === false) {
288            $trfrm->replacements[$n] = $link['link'];
289         } else {
290            $ftnt = $link['link'];
291            if (isset($footnotes[$ftnt])) {
292               $trfrm->replacements[$n] = "<A NAME=\"footnote-$ftnt\"></A><A HREF=\"#footnote-rev-$ftnt\">[$ftnt]</A>";
293            } else { // first encounter of [x]
294               $trfrm->replacements[$n] = "<A NAME=\"footnote-rev-$ftnt\"></A><SUP><A HREF=\"#footnote-$ftnt\">[$ftnt]</A></SUP>";
295               $footnotes[$ftnt] = 1;
296            }
297         }
298         $n++;
299       }
300
301       $trfrm->tokencounter = $ntok;
302       return $line;
303    }
304
305
306    // replace all URL's with tokens, so we don't confuse them
307    // with Wiki words later. Wiki words in URL's break things.
308    // URLs preceeded by a '!' are not linked
309    function wtt_urls($line, &$trfrm)
310    {
311       global $AllowedProtocols;
312
313       $n = $ntok = $trfrm->tokencounter;
314       $line = wt_tokenize($line, "!?\b($AllowedProtocols):[^\s<>\[\]\"'()]*[^\s<>\[\]\"'(),.?]", $trfrm->replacements, $ntok);
315       while ($n < $ntok) {
316         if($trfrm->replacements[$n][0] == '!')
317            $trfrm->replacements[$n] = substr($trfrm->replacements[$n], 1);
318         else
319            $trfrm->replacements[$n] = LinkURL($trfrm->replacements[$n]);
320         $n++;
321       }
322
323       $trfrm->tokencounter = $ntok;
324       return $line;
325    }
326
327
328
329
330    // Link Wiki words (BumpyText)
331    // Wikiwords preceeded by a '!' are not linked
332    function wtt_bumpylinks($line, &$trfrm)
333    {
334       global $WikiNameRegexp, $dbi;
335
336       $n = $ntok = $trfrm->tokencounter;
337       $line = wt_tokenize($line, "!?$WikiNameRegexp", $trfrm->replacements, $ntok);
338       while ($n < $ntok) {
339         $old = $trfrm->replacements[$n];
340         if ($old[0] == '!') {
341           $trfrm->replacements[$n] = substr($old,1);
342         } elseif (IsWikiPage($dbi, $old)) {
343           $trfrm->replacements[$n] = LinkExistingWikiWord($old);
344         } else {
345           $trfrm->replacements[$n] = LinkUnknownWikiWord($old);
346         }
347         $n++;
348       }
349
350       $trfrm->tokencounter = $ntok;
351       return $line;
352    }
353
354    // end of tokenizer functions
355    //////////////////////////////////////////////////////////
356
357
358    //////////////////////////////////////////////////////////
359    // basic simple markup functions
360
361    // escape HTML metachars
362    function wtm_htmlchars($line, &$transformer)
363    {
364       $line = str_replace('&', '&amp;', $line);
365       $line = str_replace('>', '&gt;', $line);
366       $line = str_replace('<', '&lt;', $line);
367       return($line);
368    }
369
370
371    // %%% are linebreaks
372    function wtm_linebreak($line, &$transformer) {
373       return str_replace('%%%', '<br>', $line);
374    }
375
376    // bold and italics
377    function wtm_bold_italics($line, &$transformer) {
378       $line = preg_replace('|(__)(.*?)(__)|', '<strong>\2</strong>', $line);
379       $line = preg_replace("|('')(.*?)('')|", '<em>\2</em>', $line);
380       return $line;
381    }
382
383
384
385    //////////////////////////////////////////////////////////
386    // some tokens to be replaced by (dynamic) content
387
388    // wiki token: title search dialog
389    function wtm_title_search($line, &$transformer) {
390       if (strpos($line, '%%Search%%') !== false) {
391          $html = LinkPhpwikiURL(
392             "phpwiki:?action=search&searchterm=()&searchtype=title",
393             gettext("Search"));
394
395          $line = str_replace('%%Search%%', $html, $line);
396       }
397       return $line;
398    }
399
400    // wiki token: fulltext search dialog
401    function wtm_fulltext_search($line, &$transformer) {
402       if (strpos($line, '%%Fullsearch%%') !== false) {
403          $html = LinkPhpwikiURL(
404             "phpwiki:?action=search&searchterm=()&searchtype=full",
405             gettext("Search"));
406
407          $line = str_replace('%%Fullsearch%%', $html, $line);
408       }
409       return $line;
410    }
411
412    // wiki token: mostpopular list
413    function wtm_mostpopular($line, &$transformer) {
414       global $ScriptUrl, $dbi;
415       if (strpos($line, '%%Mostpopular%%') !== false) {
416          $query = InitMostPopular($dbi, MOST_POPULAR_LIST_LENGTH);
417          $html = "<DL>\n";
418          while ($qhash = MostPopularNextMatch($dbi, $query)) {
419             $html .= "<DD>$qhash[hits] ... " . LinkExistingWikiWord($qhash['pagename']) . "\n";
420          }
421          $html .= "</DL>\n";
422          $line = str_replace('%%Mostpopular%%', $html, $line);
423       }
424       return $line;
425    }
426
427
428    //////////////////////////////////////////////////////////
429    // mode markup functions
430
431
432    // tabless markup for unordered, ordered, and dictionary lists
433    // ul/ol list types can be mixed, so we only look at the last
434    // character. Changes e.g. from "**#*" to "###*" go unnoticed.
435    // and wouldn't make a difference to the HTML layout anyway.
436
437    // unordered lists <UL>: "*"
438    // has to be registereed before list OL
439    function wtm_list_ul($line, &$trfrm) {
440       if (preg_match("/^([#*]*\*)[^#]/", $line, $matches)) {
441          $numtabs = strlen($matches[1]);
442          $line = preg_replace("/^([#*]*\*)/", '', $line);
443          $html = $trfrm->SetHTMLMode('ul', NESTED_LEVEL, $numtabs) . '<li>';
444          $line = $html . $line;
445       }
446       return $line;
447    }
448
449    // ordered lists <OL>: "#"
450    function wtm_list_ol($line, &$trfrm) {
451       if (preg_match("/^([#*]*\#)/", $line, $matches)) {
452          $numtabs = strlen($matches[1]);
453          $line = preg_replace("/^([#*]*\#)/", "", $line);
454          $html = $trfrm->SetHTMLMode('ol', NESTED_LEVEL, $numtabs) . '<li>';
455          $line = $html . $line;
456       }
457       return $line;
458    }
459
460
461    // definition lists <DL>: ";text:text"
462    function wtm_list_dl($line, &$trfrm) {
463       if (preg_match("/(^;+)(.*?):(.*$)/", $line, $matches)) {
464          $numtabs = strlen($matches[1]);
465          $line = $trfrm->SetHTMLMode('dl', NESTED_LEVEL, $numtabs);
466          if(trim($matches[2]))
467             $line = '<dt>' . $matches[2];
468          $line .= '<dd>' . $matches[3];
469       }
470       return $line;
471    }
472
473    // mode: preformatted text, i.e. <pre>
474    function wtm_preformatted($line, &$trfrm) {
475       if (preg_match("/^\s+/", $line)) {
476          $line = $trfrm->SetHTMLMode('pre', ZERO_LEVEL, 0) . $line;
477       }
478       return $line;
479    }
480
481    // mode: headings, i.e. <h1>, <h2>, <h3>
482    // lines starting with !,!!,!!! are headings
483    function wtm_headings($line, &$trfrm) {
484       if (preg_match("/^(!{1,3})[^!]/", $line, $whichheading)) {
485          if($whichheading[1] == '!') $heading = 'h3';
486          elseif($whichheading[1] == '!!') $heading = 'h2';
487          elseif($whichheading[1] == '!!!') $heading = 'h1';
488          $line = preg_replace("/^!+/", '', $line);
489          $line = $trfrm->SetHTMLMode($heading, ZERO_LEVEL, 0) . $line;
490       }
491       return $line;
492    }
493
494    // four or more dashes to <hr>
495    // Note this is of type WT_MODE_MARKUP becuase <hr>'s aren't
496    // allowed within <p>'s. (e.g. "<p><hr></p>" is not valid HTML.)
497    function wtm_hr($line, &$trfrm) {
498       if (preg_match('/^-{4,}(.*)$/', $line, $m)) {
499          $line = $trfrm->SetHTMLMode('', ZERO_LEVEL, 0) . '<hr>';
500          if ($m[1])
501             $line .= $trfrm->SetHTMLMode('p', ZERO_LEVEL, 0) . $m[1];
502       }
503       return $line;
504    }
505
506    // default mode: simple text paragraph
507    function wtm_paragraph($line, &$trfrm) {
508       $line = $trfrm->SetHTMLMode('p', ZERO_LEVEL, 0) . $line;
509       return $line;
510    }
511
512 // For emacs users
513 // Local Variables:
514 // mode: php
515 // c-file-style: "ellemtel"
516 // End:   
517 ?>