root/trunk/formatters/wakka.php @ 761

Revision 761, 30.2 KB (checked in by DarTar, 6 years ago)

Replacing CSS selector for data tables (.data instead of .wikka) (missed one occurrence), refs #380 and #230

  • Property svn:keywords set to Id
Line 
1<?php
2/**
3 * Wikka Formatting Engine
4 *
5 * This is the main formatting engine used by Wikka to parse wiki markup and render valid XHTML.
6 *
7 * @package             Formatters
8 * @version             $Id$
9 * @license             http://www.gnu.org/copyleft/gpl.html GNU General Public License
10 * @filesource
11 *
12 * @author      {@link http://wikkawiki.org/JsnX Jason Tourtelotte}
13 * @author      {@link http://wikkawiki.org/DotMG Mahefa Randimbisoa}
14 * @author      {@link http://wikkawiki.org/JavaWoman Marjolein Katsma}
15 * @author      {@link http://wikkawiki.org/NilsLindenberg Nils Lindenberg} (code cleanup)
16 * @author      {@link http://wikkawiki.org/DarTar Dario Taraborelli} (grab handler and filename support for codeblocks)
17 * @author      {@link http://wikkawiki.org/TormodHaugen Tormod Haugen} (table formatter support)
18 *
19 * @uses        Wakka::htmlspecialchars_ent()
20 *
21 * @todo        add support for formatter plugins;
22 * @todo        use a central RegEx library #34;
23 * @todo        add further improvements from ImprovedFormatter
24 */
25
26/**#@+
27 * Code block pattern.
28 */
29if (!defined('PATTERN_OPEN_BRACKET')) define('PATTERN_OPEN_BRACKET', '\(');
30if (!defined('PATTERN_FORMATTER')) define('PATTERN_FORMATTER', '([^;\)]+)');
31if (!defined('PATTERN_LINE_NUMBER')) define('PATTERN_LINE_NUMBER', '(;(\d*?))?');
32if (!defined('PATTERN_FILENAME')) define('PATTERN_FILENAME', '(;([^\)\x01-\x1f\*\?\"<>\|]*)([^\)]*))?');
33if (!defined('PATTERN_CLOSE_BRACKET')) define('PATTERN_CLOSE_BRACKET', '\)');
34if (!defined('PATTERN_CODE')) define('PATTERN_CODE', '(.*)');
35/**#@-*/
36
37/**
38 * Match heading tags.
39 *
40 * - $result[0] : the entire node representation, including the closing tag
41 * - $result[1] : the nodename (h1, h2, .. , h6)
42 * - $result[2] : the heading attribute, ie all the strings after the tagname and before the first ">" character
43 * - $result[3] : the content of the heading tag, just like the innerHTML method in DOM.
44 * This pattern will match only if the text it is applied to is valid XHTML: it should use lowercase in the tagName,
45 * it should not contain the character ">" inside attributes.
46 */
47if (!defined('PATTERN_MATCH_HEADINGS')) define('PATTERN_MATCH_HEADINGS', '#^<(h[1-6])(.*?)>(.*?)</\\1>$#s');
48/**
49 * Match id in attributes.
50 *
51 * - $result[0] : a string like <code>id="h1_id"</code>, starting with the letters id=, and followed by a string
52 *   enclosed in either single or double quote. It doesn't match if the term id is not preceded by any whitespace.
53 * - $result[1] : The single character used to enclose the string, either a single or a double quote.
54 * - $result[2] : The content of the string, ie the value of the id attribute.
55 * The RE uses a backref to match both single and double enclosing quotes.
56 */
57if (!defined('PATTERN_MATCH_ID_ATTRIBUTES')) define('PATTERN_MATCH_ID_ATTRIBUTES', '/(?<=\\s)id=("|\')(.*?)\\1/');
58/**
59 * The string $format_option is a semicolon separated list of strings, including the word `page'
60 */
61if (!defined('PATTERN_MATCH_PAGE_FORMATOPTION')) define('PATTERN_MATCH_PAGE_FORMATOPTION', '/(^|;)page(;|$)/');
62
63// @@@  is this condition handy? would prevent generating IDs on a page fragment
64//              - unless formatter is called on those with an explicit foprmat option!
65if (isset($format_option) && preg_match(PATTERN_MATCH_PAGE_FORMATOPTION, $format_option))
66{
67        if (!function_exists('wakka3callback'))
68        {
69                /**
70                 * "Afterburner" formatting: extra handling of already-generated XHTML code.
71                 *
72                 * 1. For headings:
73                 * a) Use heading to derive a document title
74                 * b) Ensure every heading has an id, either specified or generated. (May be
75                 * extended to generate section TOC data.)
76                 * If an id is already specified, that is used without any modification.
77                 * If no id is specified, it is generated on the basis of the heading context:
78                 * - any image tag is replaced by its alt text (if specified)
79                 * - all tags are stripped
80                 * - all characters that are not valid in an ID are stripped (except whitespace)
81                 * - the resulting string is then used by makedId() to generate an id out of it
82                 *
83                 * @access      private
84                 * @uses        Wakka::HasPageTitle()
85                 * @uses        Wakka::SetPageTitle()
86                 * @uses        Wakka::CleanTextNode()
87                 * @uses        Wakka::makeId()
88                 *
89                 * @param       array   $things required: matches of the regex in the preg_replace_callback
90                 * @return      string  heading with an id attribute
91                 */
92                function wakka3callback($things)
93                {
94                        global $wakka;
95                        $thing = $things[1];
96
97                        // heading
98                        if (preg_match(PATTERN_MATCH_HEADINGS, $thing, $matches))
99                        {
100                                list($h_element, $h_tagname, $h_attribs, $h_heading) = $matches;
101                                // @@@ apply nodeToTextOnly() on $h_heading so stored title is always valid
102                                if ((!$wakka->HasPageTitle()) && ('h5' > $h_tagname))
103                                {
104                                        $wakka->SetPageTitle($h_heading);
105                                }
106
107                                if (preg_match(PATTERN_MATCH_ID_ATTRIBUTES, $h_attribs))
108                                {
109                                        // existing id attribute: nothing to do (assume already treated as embedded code)
110                                        // @@@ we *may* want to gather ids and heading text for a TOC here ...
111                                        // heading text should then get partly the same treatment as when we're creating ids:
112                                        // at least replace images and strip tags - we can leave entities etc. alone - so we end up with
113                                        // plain text-only
114                                        // do this if we have a condition set to generate a TOC
115                                        return $h_element;
116                                }
117                                else
118                                {
119                                        // no id: we'll have to create one
120                                        $headingtext = $wakka->CleanTextNode($h_heading);               // @@@ replace with headingToTextOnly()
121                                        // now create id based on resulting heading text
122                                        $h_id = $wakka->makeId('hn', $headingtext);
123
124                                        // rebuild element, adding id
125                                        return '<'.$h_tagname.$h_attribs.' id="'.$h_id.'">'.$h_heading.'</'.$h_tagname.'>';
126                                }
127                        }
128                        // other elements to be treated go here (tables, images, code sections...)
129                }
130        }
131}
132// Note: all possible formatting tags have to be in a single regular expression for this to work correctly.
133
134if (!function_exists("wakka2callback")) # DotMG [many lines] : Unclosed tags fix!
135                                                                                # JW: NOT a complete fix!
136                                                                                # see http://wikkawiki.org/ImprovedFormatter#hn_Not_a_complete_solution
137{
138        function wakka2callback($things)
139        {
140                $thing = $things[0];
141                $result='';
142
143                static $oldIndentLevel = 0;
144                static $oldIndentLength= 0;
145                static $indentClosers = array();
146                static $newIndentSpace= array();
147                static $br = 1;
148                static $trigger_table = 0;
149                static $trigger_rowgroup = 0;
150                static $trigger_colgroup = 0;
151                static $trigger_bold = 0;
152                static $trigger_italic = 0;
153                static $trigger_underline = 0;
154                static $trigger_monospace = 0;
155                static $trigger_notes = 0;
156                static $trigger_strike = 0;
157                static $trigger_inserted = 0;
158                static $trigger_deleted = 0;
159                static $trigger_floatl = 0;
160                static $trigger_floatr = 0;
161                static $trigger_keys = 0;
162                static $trigger_strike = 0;
163                static $trigger_inserted = 0;
164                static $trigger_center = 0;
165                static $trigger_l = array(-1, 0, 0, 0, 0, 0);
166                static $output = '';
167                static $valid_filename = '';
168                static $invalid = '';
169                static $curIndentType;
170
171                global $wakka;
172
173                // @@@  inline elements should be closed before block-level elements
174                //              (see ImprovedFormatter solution again)
175                // TEST: are indents closed at end of page now??? (see... again)
176                // @@@  <kbd> is missing
177                if ((!is_array($things)) && ($things == 'closetags'))
178                {
179                        $result = '';
180                        if (3 < $trigger_table){
181                                $result .=  '</caption>';
182                        }
183                        elseif (2 < $trigger_table)
184                        {
185                                $result .=  '</th></tr>';
186                        }
187                        elseif (1 < $trigger_table)
188                        {
189                                $result .=  '</td></tr>';
190                        }
191                        if (2 < $trigger_rowgroup)
192                        {
193                                $result .=  '</tbody>';
194                        }
195                        elseif (1 < $trigger_rowgroup)
196                        {
197                                $result .=  '</tfoot>';
198                        }
199                        elseif (0 < $trigger_rowgroup)
200                        {
201                                $result .=  '</thead>';
202                        }
203                        if (0 < $trigger_table)
204                        {
205                                $result .=  '</table>';
206                        }
207
208                        if ($trigger_strike % 2)
209                        {
210                                $result .=  '</span>';
211                        }
212                        if ($trigger_notes % 2)
213                        {
214                                $result .=  '</span>';
215                        }
216                        if ($trigger_inserted % 2)
217                        {
218                                $result .=  '</span>';
219                        }
220                        if ($trigger_underline % 2)
221                        {
222                                $result .= '</span>';
223                        }
224                        if ($trigger_floatl % 2)
225                        {
226                                $result .=  '</div>';
227                        }
228                        if ($trigger_floatr % 2)
229                        {
230                                $result .=  '</div>';
231                        }
232                        if ($trigger_center % 2)
233                        {
234                                $result .=  '</div>';
235                        }
236                        if ($trigger_italic % 2)
237                        {
238                                $result .= '</em>';
239                        }
240                        if ($trigger_monospace % 2)
241                        {
242                                $result .= '</tt>';
243                        }
244                        if ($trigger_bold % 2)
245                        {
246                                $result .= '</strong>';
247                        }
248
249                        for ($i = 1; $i<=5; $i ++)
250                        {
251                                if ($trigger_l[$i] % 2) $result .=  "</h$i>";
252                        }
253
254                        $trigger_bold = $trigger_center = $trigger_floatl = $trigger_floatr = $trigger_inserted = $trigger_deleted = $trigger_italic = $trigger_keys = $trigger_table = 0;
255                        $trigger_l = array(-1, 0, 0, 0, 0, 0);
256                        $trigger_monospace = $trigger_notes = $trigger_strike = $trigger_underline = 0;
257                        return $result;
258                }
259                // Ignore the closing delimiter if there is nothing to close.
260                elseif ( preg_match("/^\|\|\n$/", $thing, $matches) && $trigger_table == 1 )
261                {
262                        return '';
263                }
264
265                // $matches[1] is element, $matches[2] is attributes, $matches[3] is styles and $matches[4] is linebreak
266                elseif ( preg_match("/^\|([^\|])?\|(\(.*?\))?(\{.*?\})?(\n)?$/", $thing, $matches) )
267                {
268                        for ( $i = 1; $i < 5; $i++ ) #38
269                        {
270                                if (!isset($matches[$i])) $matches[$i] = '';
271                        }
272                        //Set up the variables that will aggregate the html markup
273                        $close_part = '';
274                        $open_part  = '';
275                        $linebreak_after_open = '';
276                        $selfclose = '';
277                       
278                        // $trigger_table == 0 means no table, 1 means in table but no cell, 2 is in datacell, 3 is in headercell, 4 is in caption.
279
280                        //If we have parsed the caption, close it, set trigger = 1 and return.
281                        if ( $trigger_table == 4 )
282                        {
283                                $close_part = '</caption>'."\n";
284                                $trigger_table = 1;
285                                return $close_part;
286                        }
287
288                        //If we have parsed a cell - close it, go on to open new.
289                        if ( $trigger_table == 3 )
290                        {
291                                $close_part = '</th>';
292                        }
293                        elseif ( $trigger_table == 2 )
294                        {
295                                $close_part = '</td>';
296                        }
297                        // If no cell, or we want to open a table; then there is nothing to close
298                        elseif ( $trigger_table == 1 || $matches[1] == '!')
299                        {
300                                $close_part = '';
301                        }
302                        else
303                        {
304                                //This is actually opening the table (i.e. nothing at all to close). Go on to open a cell.
305                                $trigger_table = 1;
306                                $close_part = '<table class="data">'."\n";
307                        }
308
309                        //If we are in a cell and there is a linebreak - then it is end of row.
310                        if ( $trigger_table > 1 && $matches[4] == "\n" )
311                        {
312                                $trigger_table = 1;
313                                return $close_part .= '</tr>'."\n"; //Can return here, it is closed-
314                        }
315                       
316                        //If we were in a colgroup and there is a linebreak, then it is the end.
317                        if ( $trigger_colgroup == 1 && $matches[4] == "\n" )
318                        {
319                                $trigger_colgroup = 0;
320                                return $close_part .= '</colgroup>'."\n"; //Can return here, it is closed-
321                        }
322
323                        //We want to start a new table, and most likely have attributes to parse.
324                        //TODO: Need to find out if class="data" should be auto added, and if so - put it in the attribute list to add up.
325                        if ( $matches[1] == '!' )
326                        {
327                                $trigger_table = 1;
328                                $open_part = '<table class="data"';
329                                $linebreak_after_open = "\n";
330                        }
331                        //Open a caption.
332                        elseif ( $matches[1] == '?' )
333                        {
334                                $trigger_table = 4;
335                                $open_part = '<caption';
336                        }
337                        //Start a rowgroup.
338                        elseif ( $matches[1] == '#' || $matches[1] == '[' || $matches[1] == ']' )
339                        {
340                                //If we're here, we want to close any open rowgroup.
341                                if (2 < $trigger_rowgroup)
342                                {
343                                        $close_part .= '</tbody>'."\n";
344                                }
345                                elseif (1 < $trigger_rowgroup)
346                                {
347                                        $close_part .= '</tfoot>'."\n";
348                                }
349                                elseif (0 < $trigger_rowgroup)
350                                {
351                                        $close_part .= '</thead>'."\n";
352                                }
353
354                                //Then open the appropriate rowgroup.
355                                if ($matches[1] == '[' )
356                                {
357                                        $open_part .= '<thead';
358                                        $trigger_rowgroup = 1;
359                                }
360                                elseif ($matches[1] == ']' )
361                                {
362                                        $open_part .= '<tfoot';
363                                        $trigger_rowgroup = 2;
364                                }
365                                else
366                                {
367                                        $open_part .= '<tbody';
368                                        $trigger_rowgroup = 3;
369                                }
370
371                                $linebreak_after_open = "\n";
372                        }
373                        //Here we want to add colgroup.
374                        elseif ( $matches[1] == '_' )
375                        {
376                                //close any open colgroup
377                                if ( $trigger_colgroup == 1 )
378                                {
379                                        $close_part .= '</colgroup>'."\n";
380                                }
381                               
382                                $trigger_colgroup = 1;
383                                $open_part .= '<colgroup';
384                        }
385                        //And col elements
386                        elseif ( $matches[1] == '-' )
387                        {
388                                $open_part .= '<col';
389                                $selfclose = ' /';
390                                if ( $matches[4] ) $linebreak_after_open = "\n";
391                        }
392                        //Ok, then it is cells.
393                        else
394                        {
395                                $open_part = '';
396                                //Need a tbody if no other rowgroup open.
397                                if ($trigger_rowgroup == 0)
398                                {
399                                        $open_part .= '<tbody>'."\n";
400                                        $trigger_rowgroup = 3;
401                                }
402
403                                //If no row, open a new one.
404                                if ( $trigger_table == 1 )
405                                {
406                                        $open_part .= '<tr>';
407                                }
408
409                                //Header cell.
410                                if ( $matches[1] == '=' )
411                                {
412                                        $trigger_table = 3;
413                                        $open_part .= '<th';
414                                }
415                                //Datacell
416                                else
417                                {
418                                        $trigger_table = 2;
419                                        $open_part .= '<td';
420                                }
421                        }
422
423                        //If attributes...
424                        if ( preg_match("/\((.*)\)/", $matches[2], $attribs ) )
425                        {
426//                              $hints = array('core' => 'core', 'i18n' => 'i18n');
427                                $hints = array();
428                                //allow / disallow different attribute keys. (ie. data/header cell only.
429                                if ($trigger_table == 2 || $trigger_table == 3)
430                                {
431                                        $hints['cell'] = 'cell';
432                                }
433                                else
434                                {
435                                        $hints['other_table'] = 'other_table';
436                                }
437                                $open_part .= parse_attributes($attribs[1], $hints);
438                        }
439
440                        //If styles, just make attribute of it and parse again.
441                        if ( preg_match("/\{(.*)\}/", $matches[3], $attribs ) )
442                        {
443                                $attribs = "s:".$attribs[1];
444                                $open_part .= parse_attributes($attribs, array() );
445                        }
446
447                        //the variable $selfclose is "/" if this is a <col/> element.
448                        $open_part .= $selfclose.'>';
449                        return $close_part . $open_part . $linebreak_after_open;
450                }
451                //Are in table, no cell - but not asked to open new: please close and parse again. ;)
452                else if ( $trigger_table == 1 )
453                {
454                        $close_part = '';
455                        if (2 < $trigger_rowgroup)
456                        {
457                                $close_part .= '</tbody>'."\n";
458                        }
459                        elseif (1 < $trigger_rowgroup)
460                        {
461                                $close_part .= '</tfoot>'."\n";
462                        }
463                        elseif (0 < $trigger_rowgroup)
464                        {
465                                $close_part .= '</thead>'."\n";
466                        }
467
468                        $close_part .= '</table>'."\n";
469
470                        $trigger_table = $trigger_rowgroup = 0;
471
472                        //And remember to parse what we got.
473                        return $close_part.wakka2callback($things);
474                }
475
476                // convert HTML thingies
477                if ($thing == "<")
478                {
479                        return "&lt;";
480                }
481                elseif ($thing == ">")
482                {
483                        return "&gt;";
484                }
485                // float box left
486                elseif ($thing == "<<")
487                {
488                        return (++$trigger_floatl % 2 ? '<div class="floatl">' : '</div>');
489                }
490                // float box right
491                elseif ($thing == ">>")
492                {
493                        return (++$trigger_floatr % 2 ? '<div class="floatr">' : '</div>');
494                }
495                // clear floated element
496                elseif ($thing == "::c::")
497                {
498                        return ("<div class=\"clear\">&nbsp;</div>\n");
499                }
500                // keyboard
501                elseif ($thing == "#%")
502                {
503                        return (++$trigger_keys % 2 ? "<kbd class=\"keys\">" : "</kbd>");
504                }
505                // bold
506                elseif ($thing == "**")
507                {
508                        return (++$trigger_bold % 2 ? "<strong>" : "</strong>");
509                }
510                // italic
511                elseif ($thing == "//")
512                {
513                        return (++$trigger_italic % 2 ? "<em>" : "</em>");
514                }
515                // underlinue
516                elseif ($thing == "__")
517                {
518                        return (++$trigger_underline % 2 ? "<span class=\"underline\">" : "</span>");
519                }
520                // monospace
521                elseif ($thing == "##")
522                {
523                        return (++$trigger_monospace % 2 ? "<tt>" : "</tt>");
524                }
525                // notes
526                elseif ($thing == "''")
527                {
528                        return (++$trigger_notes % 2 ? "<span class=\"notes\">" : "</span>");
529                }
530                // strikethrough
531                elseif ($thing == "++")
532                {
533                        return (++$trigger_strike % 2 ? "<span class=\"strikethrough\">" : "</span>");
534                }
535                // additions
536                elseif ($thing == "&pound;&pound;")
537                {
538                        return (++$trigger_inserted % 2 ? "<ins>" : "</ins>");
539                }
540                // deletions
541                elseif ($thing == "&yen;&yen;")
542                {
543                        return (++$trigger_deleted % 2 ? "<del>" : "</del>");
544                }
545                // center
546                elseif ($thing == "@@")
547                {
548                        return (++$trigger_center % 2 ? "<div class=\"center\">\n" : "\n</div>\n");
549                }
550                // urls
551                elseif (preg_match("/^([a-z]+:\/\/\S+?)([^[:alnum:]^\/])?$/", $thing, $matches))
552                {
553                        $url = $matches[1];
554                        /* Inline images are disabled for security reason, use {{image action}} #142
555                        But if you still need this functionality, update this file like below
556                        if (preg_match("/\.(gif|jpg|png|svg)$/si", $url)) {
557                                return '<img src="'.$wakka->Link($url).'" alt="image" />'.$wakka->htmlspecialchars_ent($matches[2]);
558                        } else */
559                        // Mind Mapping Mod
560                        if (preg_match("/\.(mm)$/si", $url)) { #145
561                                return $wakka->Action("mindmap ".$url);
562                        } else
563                                return $wakka->Link($url).(isset($matches[2]) ? $matches[2] : ''); #38
564                }
565                // header level 5
566                elseif ($thing == "==")
567                {
568                                $br = 0;
569                                return (++$trigger_l[5] % 2 ? "<h5>" : "</h5>\n");
570                }
571                // header level 4
572                elseif ($thing == "===")
573                {
574                                $br = 0;
575                                return (++$trigger_l[4] % 2 ? "<h4>" : "</h4>\n");
576                }
577                // header level 3
578                elseif ($thing == "====")
579                {
580                                $br = 0;
581                                return (++$trigger_l[3] % 2 ? "<h3>" : "</h3>\n");
582                }
583                // header level 2
584                elseif ($thing == "=====")
585                {
586                                $br = 0;
587                                return (++$trigger_l[2] % 2 ? "<h2>" : "</h2>\n");
588                }
589                // header level 1
590                elseif ($thing == "======")
591                {
592                                $br = 0;
593                                return (++$trigger_l[1] % 2 ? "<h1>" : "</h1>\n");
594                }
595                // forced line breaks
596                elseif ($thing == "---")
597                {
598                        return "<br />";
599                }
600                // escaped text
601                elseif (preg_match("/^\"\"(.*)\"\"$/s", $thing, $matches))
602                {
603                        $ddquotes_policy = $wakka->GetConfigValue("double_doublequote_html");
604                        $embedded = $matches[1];
605                        if (($ddquotes_policy == 'safe') || ($ddquotes_policy == 'raw'))
606                        {
607                                // get tags with id attributes
608                                # use backref to match both single and double quotes
609                                $patTagWithId = '((<[a-z][^>]*)((?<=\\s)id=("|\')(.*?)\\4)(.*?>))';     // @@@ #34
610                                // with PREG_SET_ORDER we get an array for each match: easy to use with list()!
611                                // we do the match case-insensitive so we catch uppercase HTML as well;
612                                // SafeHTML will treat this but 'raw' may end up with invalid code!
613                                $tags2 = preg_match_all('/'.$patTagWithId.'/i', $embedded, $matches2, PREG_SET_ORDER);
614                                // step through code, replacing tags with ids with tags with new ('repaired') ids
615                                $tmpembedded = $embedded;
616                                $newembedded = '';
617                                for ($i=0; $i < $tags2; $i++)
618                                {
619                                        list( , $tag, $tagstart, $attrid, $quote, $id, $tagend) = $matches2[$i];    # $attrid not needed, just for clarity
620                                        $parts = explode($tag, $tmpembedded, 2); # split in two at matched tag
621                                        if ($id != ($newid = $wakka->makeId('embed', $id)))    # replace if we got a new value
622                                        {
623                                                $tag = $tagstart.'id='.$quote.$newid.$quote.$tagend;
624                                        }
625                                        $newembedded .= $parts[0].$tag; # append (replacement) tag to first part
626                                        $tmpembedded  = $parts[1]; # after tag: next bit to handle
627                                }
628                                $newembedded .= $tmpembedded; # add last part
629                        }
630                        switch ($ddquotes_policy)
631                        {
632                                case 'safe':
633                                        return $wakka->ReturnSafeHTML($newembedded);
634                                case 'raw':
635                                        return $newembedded; # may still be invalid code - 'raw' will not be corrected!
636                                default:
637                                        return $wakka->htmlspecialchars_ent($embedded); # display only
638                        }
639                }
640                // code text
641                elseif (preg_match("/^%%(.*?)%%$/s", $thing, $matches))
642                {
643                        /*
644                        * Note: this routine is rewritten such that (new) language formatters
645                        * will automatically be found, whether they are GeSHi language config files
646                        * or "internal" Wikka formatters.
647                        * Path to GeSHi language files and Wikka formatters MUST be defined in config.
648                        * For line numbering (GeSHi only) a starting line can be specified after the language
649                        * code, separated by a ; e.g., %%(php;27)....%%.
650                        * Specifying >= 1 turns on line numbering if this is enabled in the configuration.
651                        * An optional filename can be specified as well, e.g. %%(php;27;myfile.php)....%%
652                        * This filename will be used by the grabcode handler.                   
653                        */
654                        $output = ''; //reinitialize variables
655                        $filename = '';
656                        $valid_filename = '';
657                        $code = $matches[1];
658                        // if configuration path isn't set, make sure we'll get an invalid path so we
659                        // don't match anything in the home directory
660                        $geshi_hi_path = isset($wakka->config['geshi_languages_path']) ? $wakka->config['geshi_languages_path'] : '/:/';
661                        $wikka_hi_path = isset($wakka->config['wikka_highlighters_path']) ? $wakka->config['wikka_highlighters_path'] : '/:/';
662                        // check if a language (and an optional starting line or filename) has been specified
663                        if (preg_match('/^'.PATTERN_OPEN_BRACKET.PATTERN_FORMATTER.PATTERN_LINE_NUMBER.PATTERN_FILENAME.PATTERN_CLOSE_BRACKET.PATTERN_CODE.'$/s', $code, $matches))
664                        {
665                                list(, $language, , $start, , $filename, $invalid, $code) = $matches;
666                        }
667                        // get rid of newlines at start and end (and preceding/following whitespace)
668                        // Note: unlike trim(), this preserves any tabs at the start of the first "real" line
669                        $code = preg_replace('/^\s*\n+|\n+\s*$/','',$code);
670
671                        // check if GeSHi path is set and we have a GeSHi highlighter for this language
672                        if (isset($language) &&
673                                isset($wakka->config['geshi_path']) &&
674                                file_exists($geshi_hi_path.DIRECTORY_SEPARATOR.$language.'.php'))
675                        {
676                                // check if specified filename is valid and generate code block header
677                                if (isset($filename) &&
678                                        strlen($filename) > 0 &&
679                                        strlen($invalid) == 0) # #34 TODO: use central regex library for filename validation
680                                {
681                                        $valid_filename = $filename;
682                                        // create code block header
683                                        $output .= '<div class="code_header">';
684                                        // display filename and start line, if specified
685                                        $output .= $filename;
686                                        if (strlen($start)>0)
687                                        {
688                                                $output .= ' (line '.$start.')';
689                                        }
690                                        $output .= '</div>'."\n";
691                                }
692                                // use GeSHi for highlighting
693                                $output .= $wakka->GeSHi_Highlight($code, $language, $start);
694                        }
695                        // check Wikka highlighter path is set and if we have an internal Wikka highlighter
696                        elseif (isset($language) &&
697                                        isset($wakka->config['wikka_formatter_path']) &&
698                                        file_exists($wikka_hi_path.DIRECTORY_SEPARATOR.$language.'.php') && 
699                                        'wakka' != $language)
700                        {
701                                // use internal Wikka highlighter
702                                $output = '<div class="code">'."\n";
703                                $output .= $wakka->Format($code, $language);
704                                $output .= "</div>\n";
705                        }
706                        // no language defined or no formatter found: make default code block;
707                        // IncludeBuffered() will complain if 'code' formatter doesn't exist!
708                        else
709                        {
710                                $output = '<div class="code">'."\n";
711                                $output .= $wakka->Format($code, 'code');
712                                $output .= "</div>\n";
713                        }
714
715                        // display grab button if option is set in the config file
716                        if ($wakka->GetConfigValue('grabcode_button') == '1')   // @@@ cast to boolean and compare to TRUE
717                        {
718                                $output .= $wakka->FormOpen("grabcode");
719                                // build form
720                                $output .= '<input type="submit" class="grabcode" name="save" value="'.GRABCODE_BUTTON.'" title="'.rtrim(sprintf(GRABCODE_BUTTON_TITLE, $valid_filename)).'" />';
721                                $output .= '<input type="hidden" name="filename" value="'.urlencode($valid_filename).'" />';
722                                $output .= '<input type="hidden" name="code" value="'.urlencode($code).'" />';
723                                $output .= $wakka->FormClose();
724                        }
725                        // output
726                        return $output;
727                }
728                // forced links
729                // \S : any character that is not a whitespace character
730                // \s : any whitespace character
731                // @@@ regex accepts NO non-whitespace before whitespace, surely not correct? [[  something]]
732                else if (preg_match("/^\[\[(\S*)(\s+(.+))?\]\]$/s", $thing, $matches))          # recognize forced links across lines
733                {
734                        if (!isset($matches[1])) $matches[1] = ''; #38
735                        if (!isset($matches[3])) $matches[3] = ''; #38
736                        list (, $url, , $text) = $matches;
737                        if ($url)
738                        {
739                                //if ($url!=($url=(preg_replace("/@@|&pound;&pound;||\[\[/","",$url))))$result="</span>";
740                                if (!$text) $text = $url;
741                                //$text=preg_replace("/@@|&pound;&pound;|\[\[/","",$text);
742                                return $result.$wakka->Link($url, "", $text);
743                        }
744                        else
745                        {
746                                return "";
747                        }
748                }
749                // indented text
750                elseif (preg_match("/(^|\n)([\t~]+)(-|&|([0-9a-zA-Z]+)\))?(\n|$)/s", $thing, $matches))
751                {
752                        // new line
753                        $result .= ($br ? "<br />\n" : "\n");
754
755                        // we definitely want no line break in this one.
756                        $br = 0;
757
758                        // find out which indent type we want
759                        $newIndentType = $matches[3];
760                       
761                        if (!$newIndentType)
762                        {
763                                $opener = "<div class=\"indent\">";
764                                $closer = "</div>"; $br = 1;
765                        }
766                        elseif ($newIndentType == "-")
767                        {
768                                $opener = "<ul><li>";
769                                $closer = "</li></ul>";
770                                $li = 1;
771                        }
772                        elseif ($newIndentType == "&")
773                        {
774                                $opener = "<ul class=\"thread\"><li>";
775                                $closer = "</li></ul>";
776                                $li = 1;
777                        } #inline comments
778                        else
779                        {
780                                if (ereg('[0-9]', $newIndentType[0])) { $newIndentType = '1'; }
781                                elseif (ereg('[IVX]', $newIndentType[0])) { $newIndentType = 'I'; }
782                                elseif (ereg('[ivx]', $newIndentType[0])) { $newIndentType = 'i'; }
783                                elseif (ereg('[A-Z]', $newIndentType[0])) { $newIndentType = 'A'; }
784                                elseif (ereg('[a-z]', $newIndentType[0])) { $newIndentType = 'a'; }
785
786                                $opener = '<ol type="'.$newIndentType.'"><li>';
787                                $closer = '</li></ol>';
788                                $li = 1;
789                        }
790
791                        // get new indent level
792                        $newIndentLevel = strlen($matches[2]);
793                        if (($newIndentType != $curIndentType) && ($oldIndentLevel > 0))
794                        {
795                                for (; $oldIndentLevel > 0; $oldIndentLevel--)
796                                {
797                                        $result .= array_pop($indentClosers);
798                                }
799                        }
800                        if ($newIndentLevel > $oldIndentLevel)
801                        {
802                                for ($i = 0; $i < $newIndentLevel - $oldIndentLevel; $i++)
803                                {
804                                        $result .= $opener;
805                                        array_push($indentClosers, $closer);
806                                }
807                        }
808                        elseif ($newIndentLevel < $oldIndentLevel)
809                        {
810                                for ($i = 0; $i < $oldIndentLevel - $newIndentLevel; $i++)
811                                {
812                                        $result .= array_pop($indentClosers);
813                                }
814                        }
815
816                        $oldIndentLevel = $newIndentLevel;
817
818                        if (isset($li) && !preg_match("/".str_replace(")", "\)", $opener)."$/", $result))
819                        {
820                                $result .= "</li><li>";
821                        }
822
823                        $curIndentType = $newIndentType;
824                        return $result;
825                }
826                // new lines
827                else if ($thing == "\n")
828                {
829                        // if we got here, there was no tab in the next line; this means that we can close all open indents.
830                        $c = count($indentClosers);
831                        for ($i = 0; $i < $c; $i++)
832                        {
833                                $result .= array_pop($indentClosers);
834                                $br = 0;
835                        }
836                        $oldIndentLevel = 0;
837                        $oldIndentLength= 0;
838                        $newIndentSpace=array();
839
840                        $result .= ($br ? "<br />\n" : "\n");
841                        $br = 1;
842                        return $result;
843                }
844                // Actions
845                elseif (preg_match("/^\{\{(.*?)\}\}$/s", $thing, $matches))
846                {
847                        if ($matches[1])
848                        {
849                                return $wakka->Action($matches[1]);
850                        }
851                        else
852                        {
853                                return "{{}}";
854                        }
855                }
856                // interwiki links!
857                elseif (preg_match("/^[A-ZÄÖÜ][A-Za-zÄÖÜßäöü]+[:]\S*$/s", $thing))
858                {
859                        return $wakka->Link($thing);
860                }
861                // wiki links!
862                elseif (preg_match("/^[A-ZÄÖÜ]+[a-zßäöü]+[A-Z0-9ÄÖÜ][A-Za-z0-9ÄÖÜßäöü]*$/s", $thing))
863                {
864                        return $wakka->Link($thing);
865                }
866                // separators
867                elseif (preg_match("/-{4,}/", $thing, $matches))
868                {
869                        // TODO: This could probably be improved for situations where someone puts text on the same line as a separator.
870                        //              Which is a stupid thing to do anyway! HAW HAW! Ahem.
871                        $br = 0;
872                        return "<hr />\n";
873                }
874                // mind map xml
875                elseif (preg_match("/^<map.*<\/map>$/s", $thing))
876                {
877                        return $wakka->Action("mindmap ".$wakka->Href()."/mindmap.mm");
878                }
879                elseif ($thing[0] == '&')
880                {
881                        return ($wakka->htmlspecialchars_ent($thing));
882                }
883                // if we reach this point, it must have been an accident.
884                return $thing;
885        }
886}
887
888if (!function_exists('parse_attributes'))
889{
890        function parse_attributes($attribs, $hints) {
891
892                //Sort different attributes / keys to use for different elements.
893                static $attributes = array(
894                        'core' => array( 'c' => 'class','i' => 'id','s' => 'style','t' => 'title'),
895                        'i18n' => array( 'd' => 'dir','l' => 'xml:lang'),
896                        'cell' => array( 'a' => 'abbr','h' => 'headers','o' => 'scope','x' => 'colspan','y' => 'rowspan','z' => 'axis'),
897                        'other_table' => array( 'p' => 'span','u' => 'summary')
898                        );
899               
900                //adds in default hints ( core + i18n )
901                $hints['core'] = 'core';
902                $hints['i18n'] = 'i18n';
903
904                $attribs = preg_split('/;(?=.:)/', $attribs);
905                $return_value = '';
906
907                foreach ( $attribs as $attrib )
908                {
909                        list ($key, $value) = explode(':', $attrib, 2);
910                        foreach ( $hints as $hint )
911                        {
912                                $temp = $attributes[$hint];
913                                if ($temp) $a = $temp[$key];
914                                if ($a) break;
915                        }
916       
917                        if (!$a)
918                        {
919                                //This attribute isn't allowed here / is wrong.
920                                // WARNING: JS vulnerability: two minus signs are not allowed in a comment, so we replace any occurence of them by underscore.
921                                // Consider the code ||(p--><font size=1px><a href=...<!--:blabla
922                                // When migrating to UTF-8, we could use str_replace('--', '−−', $key) to make things more pretty. //TODO garbled ... mdash?
923                                echo '<!--Cannot find attribute for key "'.str_replace('--', '__', $key).'" from hints given.-->'."\n"; #i18n
924                        }
925                        else
926                        {
927                                // WARNING: JS vulnerability: use htmlspecialchars_ent to prevent JS attack!
928                                $return_value .= ' '.$a.'="'.$GLOBALS['wakka']->htmlspecialchars_ent($value).'"';
929                        }
930                }
931
932                return $return_value;
933        }
934}
935
936$text = str_replace("\r\n", "\n", $text);
937
938// replace 4 consecutive spaces at the beginning of a line with tab character
939// $text = preg_replace("/\n[ ]{4}/", "\n\t", $text); // moved to edit.php
940
941if ($this->handler == "show") $mind_map_pattern = "<map.*?<\/map>|"; else $mind_map_pattern = "";
942
943$text = preg_replace_callback(
944        "/".
945        "%%.*?%%|".                                                                                                                                                             # code
946        "\"\".*?\"\"|".                                                                                                                                                 # literal
947        $mind_map_pattern.
948        "\[\[[^\[]*?\]\]|".                                                                                                                                             # forced link
949        "-{3,}|".                                                                                                                                                               # forced linebreak and hr
950        "\b[a-z]+:\/\/\S+|".                                                                                                                                    # URL
951        "\*\*|\'\'|\#\#|\#\%|@@|::c::|\>\>|\<\<|&pound;&pound;|&yen;&yen;|\+\+|__|<|>|\/\/|".   # Wiki markup
952        "======|=====|====|===|==|".                                                                                                                    # headings
953        "(^|\n)[\t~]+(-(?!-)|&|([0-9]+|[a-zA-Z]+)\))?|".                                                                                # indents and lists
954        "\|(?:[^\|])?\|(?:\(.*?\))?(?:\{[^\{\}]*?\})?(?:\n)?|".                                                                 # Simple Tables
955        "\{\{.*?\}\}|".                                                                                                                                                 # action
956        "\b[A-ZÄÖÜ][A-Za-zÄÖÜßäöü]+[:](?![=_])\S*\b|".                                                                                  # InterWiki link
957        "\b([A-ZÄÖÜ]+[a-zßäöü]+[A-Z0-9ÄÖÜ][A-Za-z0-9ÄÖÜßäöü]*)\b|".                                                             # CamelWords
958        '\\&([#a-zA-Z0-9]+;)?|'. #ampersands! Track single ampersands or any htmlentity-like (&...;)
959        "\n".                                                                                                                                                                   # new line
960        "/ms", "wakka2callback", $text."\n"); #append \n (#444)
961
962// we're cutting the last <br />
963$text = preg_replace("/<br \/>$/","", $text);
964
965// @@@ don't report generation time unless some "debug mode" is on
966if (isset($format_option) && preg_match(PATTERN_MATCH_PAGE_FORMATOPTION, $format_option))
967{
968        $text .= wakka2callback('closetags');   // attempt close open tags @@@ may be needed for more than whole page!
969        $idstart = getmicrotime(TRUE);
970        $text = preg_replace_callback(
971                '#('.
972                '<h[1-6].*?>.*?</h[1-6]>'.
973                // other elements to be treated go here
974                ')#ms','wakka3callback', $text);
975        printf('<!-- Header ID generation took %.6f seconds -->', (getmicrotime(TRUE) - $idstart));     #i18n
976}
977echo $text;
978?>
Note: See TracBrowser for help on using the browser.