Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
 
3
/**
4
 * Generic & abstract parser functions & skeleton. It has some functions & generic stuff.
5
 *
6
 * @author Josep Arús
7
 *
8
 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
9
 * @package mod_wiki
10
 */
11
 
12
abstract class wiki_markup_parser extends generic_parser {
13
 
14
    protected $pretty_print = false;
15
    protected $printable = false;
16
 
17
    //page id
18
    protected $wiki_page_id;
19
 
20
    //sections
21
    protected $repeated_sections;
22
 
23
    protected $section_editing = true;
24
 
25
    //header & ToC
26
    protected $toc = array();
27
    protected $maxheaderdepth = 3;
28
 
29
    /**
30
     * function wiki_parser_link_callback($link = "")
31
     *
32
     * Returns array('content' => "Inside the link", 'url' => "http://url.com/Wiki/Entry", 'new' => false).
33
     */
34
    private $linkgeneratorcallback = array('parser_utils', 'wiki_parser_link_callback');
35
    private $linkgeneratorcallbackargs = array();
36
 
37
    /**
38
     * Table generator callback
39
     */
40
 
41
    private $tablegeneratorcallback = array('parser_utils', 'wiki_parser_table_callback');
42
 
43
    /**
44
     * Get real path from relative path
45
     */
46
    private $realpathcallback = array('parser_utils', 'wiki_parser_real_path');
47
    private $realpathcallbackargs = array();
48
 
49
    /**
50
     * Before and after parsing...
51
     */
52
 
53
    protected function before_parsing() {
54
        $this->toc = array();
55
 
56
        $this->string = preg_replace('/\r\n/', "\n", $this->string);
57
        $this->string = preg_replace('/\r/', "\n", $this->string);
58
 
59
        $this->string .= "\n\n";
60
 
61
        if (!$this->printable && $this->section_editing) {
62
            $this->returnvalues['unparsed_text'] = $this->string;
63
            $this->string = $this->get_repeated_sections($this->string);
64
        }
65
    }
66
 
67
    protected function after_parsing() {
68
        if (!$this->printable) {
69
            $this->returnvalues['repeated_sections'] = array_unique($this->returnvalues['repeated_sections']);
70
        }
71
 
72
        $this->process_toc();
73
 
74
        $this->string = preg_replace("/\n\s/", "\n", $this->string);
75
        $this->string = preg_replace("/\n{2,}/", "\n", $this->string);
76
        $this->string = trim($this->string);
77
        $this->string .= "\n";
78
    }
79
 
80
    /**
81
     * Set options
82
     */
83
 
84
    protected function set_options($options) {
85
        parent::set_options($options);
86
 
87
        $this->returnvalues['link_count'] = array();
88
        $this->returnvalues['repeated_sections'] = array();
89
        $this->returnvalues['toc'] = "";
90
 
91
        foreach ($options as $name => $o) {
92
            switch ($name) {
93
            case 'link_callback':
94
                $callback = explode(':', $o);
95
 
96
                global $CFG;
97
                require_once($CFG->dirroot . $callback[0]);
98
 
99
                if (function_exists($callback[1])) {
100
                    $this->linkgeneratorcallback = $callback[1];
101
                }
102
                break;
103
            case 'link_callback_args':
104
                if (is_array($o)) {
105
                    $this->linkgeneratorcallbackargs = $o;
106
                }
107
                break;
108
            case 'real_path_callback':
109
                $callback = explode(':', $o);
110
 
111
                global $CFG;
112
                require_once($CFG->dirroot . $callback[0]);
113
 
114
                if (function_exists($callback[1])) {
115
                    $this->realpathcallback = $callback[1];
116
                }
117
                break;
118
            case 'real_path_callback_args':
119
                if (is_array($o)) {
120
                    $this->realpathcallbackargs = $o;
121
                }
122
                break;
123
            case 'table_callback':
124
                $callback = explode(':', $o);
125
 
126
                global $CFG;
127
                require_once($CFG->dirroot . $callback[0]);
128
 
129
                if (function_exists($callback[1])) {
130
                    $this->tablegeneratorcallback = $callback[1];
131
                }
132
                break;
133
            case 'pretty_print':
134
                if ($o) {
135
                    $this->pretty_print = true;
136
                }
137
                break;
138
            case 'pageid':
139
                $this->wiki_page_id = $o;
140
                break;
141
            case 'printable':
142
                if ($o) {
143
                    $this->printable = true;
144
                }
145
                break;
146
            }
147
        }
148
    }
149
 
150
    /**
151
     * Generic block rules
152
     */
153
 
154
    protected function line_break_block_rule($match) {
155
        return '<hr />';
156
    }
157
 
158
    protected function list_block_rule($match) {
159
        preg_match_all("/^\ *([\*\#]{1,5})\ *((?:[^\n]|\n(?!(?:\ *[\*\#])|\n))+)/im", $match[1], $listitems, PREG_SET_ORDER);
160
 
161
        return $this->process_block_list($listitems) . $match[2];
162
    }
163
 
164
    protected function nowiki_block_rule($match) {
165
        return parser_utils::h('pre', $this->protect($match[1]));
166
    }
167
 
168
    /**
169
     * Generic tag rules
170
     */
171
 
172
    protected function nowiki_tag_rule($match) {
173
        return parser_utils::h('tt', $this->protect($match[1]));
174
    }
175
 
176
    /**
177
     * Header generation
178
     */
179
 
180
    protected function generate_header($text, $level) {
181
        $toctext = $text = trim($text);
182
 
183
        if (!$this->pretty_print && $level == 1) {
184
            $editlink = '[' . get_string('editsection', 'wiki') . ']';
185
            $url = array('href' => "edit.php?pageid={$this->wiki_page_id}&section=" . urlencode($text),
186
                'class' => 'wiki_edit_section');
187
            $text .= ' ' . parser_utils::h('a', $this->protect($editlink), $url);
188
            $toctext .= ' ' . parser_utils::h('a', $editlink, $url);
189
        }
190
 
191
        if ($level <= $this->maxheaderdepth) {
192
            $this->toc[] = array($level, $toctext);
193
            $num = count($this->toc);
194
            $text = parser_utils::h('a', "", array('name' => "toc-$num")) . $text;
195
        }
196
 
197
        // Display headers as <h3> and lower for accessibility.
198
        return parser_utils::h('h' . min(6, $level + 2), $text) . "\n\n";
199
    }
200
 
201
    /**
202
     * Table of contents processing after parsing
203
     */
204
    protected function process_toc() {
205
        if (empty($this->toc)) {
206
            return;
207
        }
208
 
209
        $toc = "";
210
        $currentsection = array(0, 0, 0);
211
        $i = 1;
212
        foreach ($this->toc as & $header) {
213
            switch ($header[0]) {
214
            case 1:
215
                $currentsection = array($currentsection[0] + 1, 0, 0);
216
                break;
217
            case 2:
218
                $currentsection[1]++;
219
                $currentsection[2] = 0;
220
                if ($currentsection[0] == 0) {
221
                    $currentsection[0]++;
222
                }
223
                break;
224
            case 3:
225
                $currentsection[2]++;
226
                if ($currentsection[1] == 0) {
227
                    $currentsection[1]++;
228
                }
229
                if ($currentsection[0] == 0) {
230
                    $currentsection[0]++;
231
                }
232
                break;
233
            default:
234
                continue 2;
235
            }
236
            $number = "$currentsection[0]";
237
            if (!empty($currentsection[1])) {
238
                $number .= ".$currentsection[1]";
239
                if (!empty($currentsection[2])) {
240
                    $number .= ".$currentsection[2]";
241
                }
242
            }
243
            $toc .= parser_utils::h('p', $number . ". " .
244
               parser_utils::h('a', str_replace(array('[[', ']]'), '', $header[1]), array('href' => "#toc-$i")),
245
               array('class' => 'wiki-toc-section-' . $header[0] . " wiki-toc-section"));
246
            $i++;
247
        }
248
 
249
        $this->returnvalues['toc'] = "<div class=\"wiki-toc\"><p class=\"wiki-toc-title\">" . get_string('tableofcontents', 'wiki') . "</p>$toc</div>";
250
    }
251
 
252
    /**
253
     * List helpers
254
     */
255
 
256
    private function process_block_list($listitems) {
257
        $list = array();
258
        foreach ($listitems as $li) {
259
            $text = str_replace("\n", "", $li[2]);
260
            $this->rules($text);
261
 
262
            if ($li[1][0] == '*') {
263
                $type = 'ul';
264
            } else {
265
                $type = 'ol';
266
            }
267
 
268
            $list[] = array(strlen($li[1]), $text, $type);
269
        }
270
        $type = $list[0][2];
271
        return "<$type>" . "\n" . $this->generate_list($list) . "\n" . "</$type>" . "\n";
272
    }
273
 
274
    /**
275
     * List generation function from an array of array(level, text)
276
     */
277
 
278
    protected function generate_list($listitems) {
279
        $list = "";
280
        $current_depth = 1;
281
        $next_depth = 1;
282
        $liststack = array();
283
        for ($lc = 0; $lc < count($listitems) && $next_depth; $lc++) {
284
            $cli = $listitems[$lc];
285
            $nli = isset($listitems[$lc + 1]) ? $listitems[$lc + 1] : null;
286
 
287
            $text = $cli[1];
288
 
289
            $current_depth = $next_depth;
290
            $next_depth = $nli ? $nli[0] : null;
291
 
292
            if ($next_depth == $current_depth || $next_depth == null) {
293
                $list .= parser_utils::h('li', $text) . "\n";
294
            } else if ($next_depth > $current_depth) {
295
                $next_depth = $current_depth + 1;
296
 
297
                $list .= "<li>" . $text . "\n";
298
                $list .= "<" . $nli[2] . ">" . "\n";
299
                $liststack[] = $nli[2];
300
            } else {
301
                $list .= parser_utils::h('li', $text) . "\n";
302
 
303
                for ($lv = $next_depth; $lv < $current_depth; $lv++) {
304
                    $type = array_pop($liststack);
305
                    $list .= "</$type>" . "\n" . "</li>" . "\n";
306
                }
307
            }
308
        }
309
 
310
        for ($lv = 1; $lv < $current_depth; $lv++) {
311
            $type = array_pop($liststack);
312
            $list .= "</$type>" . "\n" . "</li>" . "\n";
313
        }
314
 
315
        return $list;
316
    }
317
 
318
    /**
319
     * Table generation functions
320
     */
321
 
322
    protected function generate_table($table) {
323
        $table_html = call_user_func_array($this->tablegeneratorcallback, array($table));
324
 
325
        return $table_html;
326
    }
327
 
328
    protected function format_image($src, $alt, $caption = "", $align = 'left') {
329
        $src = $this->real_path($src);
330
        return parser_utils::h('div', parser_utils::h('p', $caption) . '<img src="' . $src . '" alt="' . $alt . '" />', array('class' => 'wiki_image_' . $align));
331
    }
332
 
333
    protected function real_path($url) {
334
        $callbackargs = array_merge(array($url), $this->realpathcallbackargs);
335
        return call_user_func_array($this->realpathcallback, array_values($callbackargs));
336
    }
337
 
338
    /**
339
     * Link internal callback
340
     */
341
 
342
    protected function link($link, $anchor = "") {
343
        $link = trim($link);
344
        if (preg_match("/^(https?|s?ftp):\/\/.+$/i", $link)) {
345
            $link = trim($link, ",.?!");
346
            return array('content' => $link, 'url' => $link);
347
        } else {
348
            $callbackargs = $this->linkgeneratorcallbackargs;
349
            $callbackargs['anchor'] = $anchor;
350
            $link = call_user_func_array($this->linkgeneratorcallback, array($link, $callbackargs));
351
 
352
            if (isset($link['link_info'])) {
353
                $l = $link['link_info']['link'];
354
                unset($link['link_info']['link']);
355
                $this->returnvalues['link_count'][$l] = $link['link_info'];
356
            }
357
            return $link;
358
        }
359
    }
360
 
361
    /**
362
     * Format links
363
     */
364
 
365
    protected function format_link($text) {
366
        $matches = array();
367
        if (preg_match("/^([^\|]+)\|(.+)$/i", $text, $matches)) {
368
            $link = $matches[1];
369
            $content = trim($matches[2]);
370
            if (preg_match("/(.+)#(.*)/is", $link, $matches)) {
371
                $link = $this->link($matches[1], $matches[2]);
372
            } else if ($link[0] == '#') {
373
                $link = array('url' => "#" . urlencode(substr($link, 1)));
374
            } else {
375
                $link = $this->link($link);
376
            }
377
 
378
            $link['content'] = $content;
379
        } else {
380
            $link = $this->link($text);
381
        }
382
 
383
        if (isset($link['new']) && $link['new']) {
384
            $options = array('class' => 'wiki_newentry');
385
        } else {
386
            $options = array();
387
        }
388
 
389
        $link['content'] = $this->protect($link['content']);
390
        $link['url'] = $this->protect($link['url']);
391
 
392
        $options['href'] = $link['url'];
393
 
394
        if ($this->printable) {
395
            $options['href'] = '#'; //no target for the link
396
            }
397
        return array($link['content'], $options);
398
    }
399
 
400
    /**
401
     * Section editing
402
     */
403
 
404
    public function get_section($header, $text, $clean = false) {
405
        if ($clean) {
406
            $text = preg_replace('/\r\n/', "\n", $text);
407
            $text = preg_replace('/\r/', "\n", $text);
408
            $text .= "\n\n";
409
        }
410
 
411
        $regex = "/(.*?)(=\ *".preg_quote($header, '/')."\ *=*\n.*?)((?:\n=[^=]+.*)|$)/is";
412
        preg_match($regex, $text, $match);
413
 
414
        if (!empty($match)) {
415
            return array($match[1], $match[2], $match[3]);
416
        } else {
417
            return false;
418
        }
419
    }
420
 
421
    protected function get_repeated_sections(&$text, $repeated = array()) {
422
        $this->repeated_sections = $repeated;
423
        return preg_replace_callback($this->blockrules['header']['expression'], array($this, 'get_repeated_sections_callback'), $text);
424
    }
425
 
426
    protected function get_repeated_sections_callback($match) {
427
        $num = strlen($match[1]);
428
        $text = trim($match[2]);
429
        if ($num == 1) {
430
            if (in_array($text, $this->repeated_sections)) {
431
                $this->returnvalues['repeated_sections'][] = $text;
432
                return $text . "\n";
433
            } else {
434
                $this->repeated_sections[] = $text;
435
            }
436
        }
437
 
438
        return $match[0];
439
    }
440
 
441
}