root/lang/php/mumu/trunk/mumu.php @ 2571

Revision 2571, 39.5 kB (checked in by tasuku, 5 years ago)

r299@dhcp158 (orig r42): tasuku | 2007-09-11 21:07:32 +0900
oops...


Line 
1<?php
2// mumu the template engine (c) 2007- Brazil, Inc.
3// originally developed by Tasuku SUENAGA a.k.a. gunyarakun
4/*
5Copyright (c) 2005, the Lawrence Journal-World
6Copyright (c) 2007-, Brazil, Inc.
7All rights reserved.
8
9Redistribution and use in source and binary forms, with or without
10modification, are permitted provided that the following conditions
11are met:
12
13  1. Redistributions of source code must retain the above copyright notice,
14     this list of conditions and the following disclaimer.
15
16  2. Redistributions in binary form must reproduce the above copyright
17     notice, this list of conditions and the following disclaimer in the
18     documentation and/or other materials provided with the distribution.
19
20  3. Neither the name of Django nor the names of its contributors may be used
21     to endorse or promote products derived from this software without
22     specific prior written permission.
23
24THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
25"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
26LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
27FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
28COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
29INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
30BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
31LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
32CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
34ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
35POSSIBILITY OF SUCH DAMAGE.
36*/
37
38// ��
39// ��� {{ ���}}             : ����ִ�
40
41// �֥���
42// {% include "filename" %} : �ƥ�졼�ȤΥ��󥯥롼��// {% extends "filename" %} : �ƥ�졼�Ȥγ��
43// {% block blockname %}    : �֥����λϤޤ�/ {% endblock %}           : �֥����ν���
44// {% for item in items %}  : ���ms����em�����
45// {% endfor %}             : for�ν���
46// {% cycle val1,val2 %}    : for�롼�פ��val1,val2��ߤ˽Ф�(ɽ�ǹԤ���طʿ��Ȥ�)
47// {% if cond %} {% else %} : cond���������줿�Ȥ��������// {% endif %}              : if�ν���
48// {% debug %}              : �ƥ�졼�Ȥ�Ϥ��줿�������/ {% now "format" %}       : ���ߤ���������ޥåȤǽ�Ϥ��ޤ�
49// {% filter fil1|fil2 %}   : �֥������������륿�ˤ����ޤ�
50// {% endfilter %}          : filter�ν���
51// {# comment #}            : ������
52// �ѥ���// {{ ����ѥ���|�ѥ��� }} : ���ե��륿���ƽ�Ϥ���/ |addslashes                  : \��ˡ�JavaScript���Ϥ�����������
53// |length                      : �������
54// |escape                      : %<>"'�Υ���������// |stringformat:"format"       : ��ꤷ���ե����ޥåȤ�ͤ�����ޥå�// |urlencode                   : url���󥳡���// |linebreaksbr                : ���Ԥ� />��Ѵ�
55// |date:"format"               : ��ꤷ���ե����ޥåȤ��ե����ޥå�// |join:"str"                  : str����������// ������ forloop.counter     : ���ߤΥ롼�ײ�ֹ�1 ����������)
56// forloop.counter0    : ���ߤΥ롼�ײ�ֹ�0 ����������)
57// forloop.revcounter  : �����������롼�ײ�ֹ�1 ����������)
58// forloop.revcounter0 : �����������롼�ײ�ֹ�0 ����������)
59// forloop.first       : �ǽ��롼�פǤ��� true �ˤʤ���
60// forloop.last        : �Ǹ��롼�פǤ��� true �ˤʤ���
61// forloop.parentloop  : ����Υ롼�פξ��������롼�פ���ޤ�
62// block.super         : �ƥƥ�졼�Ȥ�lock��������������ä���������
63
64// ����/ FIXME�����褦�ˡ�
65// �������嵡���Ȥ�������͡��ѡ����Ѥߤι�¤�򥷥ꥢ�饤�����롩
66
67class MuUtil {
68  public static function getpath($basepath, $path) {
69    $o = getcwd();
70    chdir(dirname($basepath));
71    $r = realpath($path);
72    chdir($o);
73    return $r;
74  }
75}
76
77class MuContext {
78  // �ƥ�졼�Ȥ��Ϥ���ξ�����饹
79  private $dicts;
80  const VARIABLE_ATTRIBUTE_SEPARATOR = '.';
81  function __construct($dict = array()) {
82    $this->dicts = array($dict);
83  }
84  // �ɥå���������������
85  function resolve($expr) {
86    $bits = explode(self::VARIABLE_ATTRIBUTE_SEPARATOR, $expr);
87    $current = $this->get($bits[0]);
88    array_shift($bits);
89    while ($bits) {
90      if (is_array($current) && array_key_exists($bits[0], $current)) {
91        // array���������(�������⥳����)
92        $current = $current[$bits[0]];
93      } elseif (method_exists($current, $bits[0])) {
94        // �᥽�åɥ�����       if (($current = call_user_func(array($current, $bits[0]))) === FALSE) {
95          return 'method call error';
96        }
97      } else {
98        return 'resolve error';
99      }
100      array_shift($bits);
101    }
102    return $current;
103  }
104  function has_key($key) {
105    foreach ($this->dicts as $dict) {
106      if (array_key_exists($key, $dict)) {
107        return True;
108      }
109    }
110    return False;
111  }
112  function get($key) {
113    foreach ($this->dicts as $dict) {
114      if (array_key_exists($key, $dict)) {
115        return $dict[$key];
116      }
117    }
118    return null;
119  }
120  function set($key, $value) {
121    $this->dicts[0][$key] = $value;
122  }
123  function push() {
124    array_unshift($this->dicts, array());
125  }
126  function pop() {
127    array_shift($this->dicts);
128  }
129  function update($other_array) {
130    array_unshift($this->dicts, $other_array);
131  }
132}
133
134class MuNode {
135  public function _render() {
136    return '';
137  }
138}
139
140class MuNodeList {
141  private $nodes;
142  function __construct() {
143    $this->nodes = array();
144  }
145  public function _render($context) {
146    $bits = array();
147    foreach ($this->nodes as $node) {
148      array_push($bits, $node->_render($context));
149    }
150    return implode('', $bits);
151  }
152  public function push($node) {
153    array_push($this->nodes, $node);
154  }
155}
156
157class MuErrorNode extends MuNode {
158  private $errorCode;
159  private $filename;
160  private $linenumber;
161  // TODO: php5 don't support array as const
162  private static $errorMsg = array(
163    'without_closetag_tag' => 'Cannot find %} !', // TODO: remove const
164    'without_closetag_variable' => 'Cannot find }} !',
165    'without_closetag_comment' => 'Cannot find #} !',
166    'invalidfilename_extends_tag' => 'Invalid filename specified with extends tag',
167    'invalidfilename_include_tag' => 'Invalid filename specified with include tag',
168    'multiple_extends_tag' => 'Only 1 extends tag are allowed to be specified',
169    'numofparam_extends_tag' => 'Number of parameters are invalid to extends tag',
170    'invalidparam_extends_tag' => 'Invalid parameter(s) specified to extends tag',
171    'numofparam_include_tag' => 'Number of parameters are invalid to include tag',
172    'invalidparam_include_tag' => 'Invalid parameter(s) specified to include tag',
173    'multiple_block_tag' => 'Only 1 block tag can exists as the same name',
174    'numofparam_for_tag' => 'Number of parameters are invalid to for tag',
175    'invalidparam_for_tag' => 'Invalid parameter(s) specified to for tag',
176    'numofparam_cycle_tag' => 'Number of parameters are invalid to cycle tag',
177    'invalidparam_cycle_tag' => 'Invalid parameter(s) specified to cycle tag',
178    'numofparam_if_tag' => 'Number of parameters are invalid to if tag',
179    'andormixed_if_tag' => 'In if condition and/or is not allowed to be mixed',
180    'invalidparam_if_tag' => 'Invalid parameter(s) specified to if tag',
181    'numofparam_now_tag' => 'Number of parameters are invalid to now tag',
182    'invalidparam_now_tag' => 'Invalid parameter(s) specified to now tag',
183    'numofparam_filter_tag' => 'Number of parameters are invalid to filter tag',
184    'invalidparam_filter_tag' => 'Invalid parameter(s) specified to filter tag',
185    'invalidparam_filter_variable' => 'Invalid filter name',
186    'unknown_tag' => 'Unknown tag is specified',
187    'unknown' => 'Unknown error. Maybe bugs in MuMu'
188  );
189  function __construct($errorCode, $filename, $linenumber) {
190    if (array_key_exists($errorCode, self::$errorMsg)) {
191      $this->errorCode = $errorCode;
192    } else {
193      $this->errorCode = 'unknown';
194    }
195    $this->filename = $filename;
196    $this->linenumber = $linenumber;
197  }
198  public function _render($context) {
199    return 'file: '. $this->filename .' line: '. $this->linenumber .' '.
200           self::$errorMsg[$this->errorCode];
201  }
202}
203
204// 1�ĤΥƥ�졼�Ȥ������������
205class MuFile extends MuNode {
206  public $nodelist;        // �ե������ѡ�������NodeList
207  private $block_dict;      // nodelist�������ock̾ => MuBlockNode(�λ���
208  private $parent_tfile;    // extends���������ƥƥ�졼��  function __construct($nodelist, $block_dict, $parentPath = null, $path = null) {
209    $this->nodelist = $nodelist;
210    $this->block_dict = $block_dict;
211    if ($parentPath && $path) {
212      $epath = MuUtil::getpath($path, $parentPath);
213      if (($this->parent_tfile = MuParser::parse_from_file($epath)) === FALSE) {
214        // TODO: ���顼���������ƥ�졼�������˶����Ƥ�����     }
215    }
216  }
217  public function render($raw_context) {
218    return $this->_render(new MuContext($raw_context));
219  }
220  public function _render($context) {
221    if ($this->parent_tfile) {
222      foreach ($this->block_dict as $blockname => $blocknode) {
223        if (($parent_block = $this->parent_tfile->get_block($blockname)) === FALSE) {
224          if ($this->parent_tfile->is_child()) {
225            $this->parent_tfile->append_block($blocknode);
226          }
227        } else {
228          $parent_block->parent = $blocknode->parent;
229          $parent_block->add_parent($parent_block->nodelist);
230          $parent_block->nodelist = $blocknode->nodelist;
231        }
232      }
233      return $this->parent_tfile->_render($context);
234    } else {
235      return $this->nodelist->_render($context);
236    }
237  }
238  public function get_block($blockname) {
239    if (array_key_exists($blockname, $this->block_dict)) {
240      return $this->block_dict[$blockname];
241    } else {
242      return false;
243    }
244  }
245  public function append_block($blocknode) {
246    // ¹������줿�֥���̾�ǡ��Ƥˤ���������ʤ�����
247    // �ƤοƤˤ���������������ᡢ
248    // ¹�����˥֥������
249    $this->nodelist->push($blocknode);
250    $this->block_dict[$blocknode->name] = $blocknode;
251  }
252  public function is_child() {
253    return ($parent_tfile !== FALSE);
254  }
255}
256
257class MuTextNode extends MuNode {
258  private $text;
259  function __construct($text) {
260    $this->text = $text;
261  }
262  public function _render($context) {
263    return $this->text;
264  }
265}
266
267class MuVariableNode extends MuNode {
268  private $filter_expression;
269  function __construct($filter_expression) {
270    $this->filter_expression = $filter_expression;
271  }
272  public function _render($context) {
273    return $this->filter_expression->resolve($context);
274  }
275}
276
277class MuIncludeNode extends MuNode {
278  private $tplfile;
279  function __construct($includePath) {
280    // FIXME: �������ƥ�����å���̵�¥롼�ץ���å�
281    if (($this->tplfile = MuParser::parse_from_file($includePath)) === FALSE) {
282      // TODO: ���顼���������ƥ�졼�������˶����Ƥ�����     $this->tplfile = $this->make_errornode('invalidfilename_include');
283    }
284  }
285  public function _render($context) {
286    return $this->tplfile->_render($context);
287  }
288}
289
290class MuBlockNode extends MuNode {
291  public $name;
292  public $nodelist;
293  public $parent;
294
295  private $context; // for block.super
296
297  function __construct($name, $nodelist, $parent = Null) {
298    $this->name = $name;
299    $this->nodelist = $nodelist;
300    $this->parent = $parent;
301  }
302  public function _render($context) {
303    $context->push();
304    $this->context = $context;
305    $context->set('block', $this); // block.super�
306    $res = $this->nodelist->_render($context);
307    $context->pop();
308    return $res;
309  }
310  public function super() {
311    if ($this->parent) {
312      return $this->parent->_render($this->context);
313    }
314    return '';
315  }
316  public function add_parent($nodelist) {
317    if ($this->parent) {
318      $this->parent->add_parent($nodelist);
319    } else {
320      $this->parent = new MuBlockNode($this->name, $this->nodelist);
321    }
322  }
323}
324
325class MuCycleNode extends MuNode {
326  private $cyclevars;
327  private $cyclevars_len;
328  private $variable_name;
329
330  function __construct($cyclevars, $variable_name = null) {
331    $this->cyclevars = $cyclevars;
332    $this->cyclevars_len = count($cyclevars);
333    $this->counter = -1;
334    $this->variable_name = $variable_name;
335  }
336
337  function _render($context) {
338    $this->counter++;
339    $value = $this->cyclevars[$this->counter % $this->cyclevars_len];
340    if ($this->variable_name) {
341      $context.set($this->variable_name, $value);
342    }
343    return $value;
344  }
345}
346
347class MuDebugNode extends MuNode {
348  function _render($context) {
349    ob_start();
350    echo "Context\n";
351    var_dump($context);
352    echo "\$_SERVER\n";
353    var_dump($_SERVER);
354    echo "\$_GET\n";
355    var_dump($_GET);
356    echo "\$_POST\n";
357    var_dump($_POST);
358    echo "\$_COOKIE\n";
359    var_dump($_COOKIE);
360    $output = ob_get_contents();
361    ob_end_clean();
362    return $output;
363  }
364}
365
366class MuFilterNode extends MuNode {
367  private $filter_expr;
368  private $nodelist;
369  function __construct($filter_expr, $nodelist) {
370    $this->filter_expr = $filter_expr;
371    $this->nodelist = $nodelist;
372  }
373  function _render($context) {
374    $output = $this->nodelist->_render($context);
375    $context->update(array('var' => $output));
376    $filtered = $this->filter_expr->resolve($context);
377    $context->pop();
378    return $filtered;
379  }
380}
381
382class MuForNode extends MuNode {
383  private $loopvar;
384  private $sequence;
385  private $reversed;
386  private $nodelist_loop;
387
388  function __construct($loopvar, $sequence, $reversed, $nodelist_loop) {
389    $this->loopvar = $loopvar;
390    $this->sequence = $sequence;
391    $this->reversed = $reversed;
392    $this->nodelist_loop = $nodelist_loop;
393  }
394  function _render($context) {
395    if ($context->has_key('forloop')) {
396      $parentloop = $context->get('forloop');
397    } else {
398      $parentloop = new MuContext();
399    }
400    $context->push();
401    if (!($values = $context->resolve($this->sequence))) {
402      $values = array();
403    }
404    if (!is_array($values)) {
405      $values = array($value);
406    }
407    $len_values = count($values);
408    // FIXME: $this->reversed
409    $rnodelist = array();
410    for ($i = 0; $i < $len_values; $i++) {
411      $context->set('forloop', array(
412        'counter0' => $i,
413        'counter' => $i + 1,
414        'revcounter' => $len_values - $i,
415        'revcounter0' => $len_values - $i - 1,
416        'first' => ($i == 0),
417        'last' => ($i == ($len_values - 1)),
418        'parentloop' => $parentloop
419      ));
420      $context->set($this->loopvar, $values[$i]);
421      array_push($rnodelist, $this->nodelist_loop->_render($context));
422    }
423    $context->pop();
424    return implode('', $rnodelist);
425  }
426}
427
428class MuIfNode extends MuNode {
429  private $bool_exprs;
430  private $nodelist_true;
431  private $nodelist_false;
432  private $link_type;
433
434  const LINKTYPE_AND = 0;
435  const LINKTYPE_OR  = 1;
436
437  function __construct($bool_exprs, $nodelist_true, $nodelist_false, $link_type) {
438    $this->bool_exprs = $bool_exprs;
439    $this->nodelist_true = $nodelist_true;
440    $this->nodelist_false = $nodelist_false;
441    $this->link_type = $link_type;
442  }
443  function _render($context) {
444    if ($this->link_type == self::LINKTYPE_OR) {
445      foreach ($this->bool_exprs as $be) {
446        list($ifnot, $bool_expr) = $be;
447        $value = $context->resolve($bool_expr);
448        if (($value && !$ifnot) || ($ifnot && !$value)) {
449          return $this->nodelist_true->_render($context);
450        }
451      }
452      return $this->nodelist_false->_render($context);
453    } else { // self::LINKTYPE_AND
454      foreach ($this->bool_exprs as $be) {
455        list($ifnot, $bool_expr) = $be;
456        $value = $context->resolve($bool_expr);
457        if (!(($value && !$ifnot) || ($ifnot && !$value))) {
458          return $this->nodelist_false->_render($context);
459        }
460      }
461      return $this->nodelist_true->_render($context);
462    }
463  }
464}
465
466class MuNowNode extends MuNode {
467  private $format_string;
468  function __construct($format_string) {
469    $this->format_string = $format_string;
470  }
471  public function _render($context) {
472    return date($this->format_string);
473  }
474}
475
476class MuFilterExpression {
477  private $var;
478  private $filters;
479
480  // TODO: php5 don't support array as const...
481  private static $valid_filternames = array (
482    'addslashes',
483    'length',
484    'escape',
485    'stringformat',
486    'urlencode',
487    'linebreaksbr',
488    'date',
489    'join',
490  );
491
492  function __construct($token) {
493    // $token = 'variable|default:"Default value"|date:"Y-m-d"'
494    // �äƤΤ����ä��顢
495    // $this->var = 'variable'
496    // $this->filters = 'array(array('default, 'Default value'), array('date', 'Y-m-d'))'
497    // �äƤ��롣
498    // Django�Τ��ǻϤޤä��餤���ʤ��餷����
499
500    $fils = MuParser::smart_split(trim($token), '|', False, True);
501    $this->var = array_shift($fils);
502    $this->filters = array();
503    foreach ($fils as $fil) {
504      $f = MuParser::smart_split($fil, ':', True, False);
505      if (in_array($f[0], self::$valid_filternames)) {
506        array_push($this->filters, $f);
507      }
508    }
509  }
510
511  // TODO: support ignore_failures
512  public function resolve($context) {
513    // eval�Ȥ�call_user_func_array������witch-case��ispatch�����ɤ�����    $val = $context->resolve($this->var);
514    foreach ($this->filters as $fil) {
515      // TODO: �����å�
516      switch ($fil[0]) {
517        case 'addslashes':
518          $val = addslashes($val);
519          break;
520        case 'length':
521          # array��ount��string��trlen
522          if (is_array($val)) {
523            $val = count($val);
524          } else if (is_string($val)) {
525            $val = strlen($val);
526          }
527          break;
528        case 'escape':
529          $val = htmlspecialchars($val);
530          break;
531        case 'stringformat':
532          // $fil[1]�˥���ʸ���ʤ��褦�˵���������         $val = sprintf($fil[1], $val);
533          break;
534        case 'urlencode':
535          $val = urlencode($val);
536          break;
537        case 'linebreaksbr':
538          $val = nl2br($val);
539          break;
540        case 'date':
541          $val = $val instanceof DateTime ? $val : new DateTime($val);
542          $val = $val->format($fil[1]);
543          break;
544        case 'join':
545          if (is_array($val)) {
546            $val = implode($fil[1], $val);
547          }
548          break;
549        default:
550          // �ɤ�ե��륿̾���ޥ����ä��������Ƥ����������ɡ�
551          // �������������äƤ��ޥ���������ڤ�
552          // TODO: �ե��륿̾�򥢥����٥åȤ��Ȥ��Τߤ˥ե��륿���Ƥ���
553          $val = 'unknown filter specified';
554      }
555    }
556    return $val;
557  }
558}
559
560class MuParser {
561  private $template;             // �ѡ�����Υƥ�졼����� private $template_len;         // �ƥ�졼�����Ĺ��
562  public $templatePath;         // �ƥ�졼�ȤΥѥ�(����)
563  private $block_dict = array(); // block���� => block�ؤλ���  private $extends = false;      // extends�ξ��Υե�����
564  private $spos = 0;             // ���ߥѡ�����ΰ��
565  # template syntax constants
566  const FILTER_SEPARATOR = '|';
567  const FILTER_ARGUMENT_SEPARATOR = ':';
568  const VARIABLE_ATTRIBUTE_SEPARATOR = '.';
569  const BLOCK_TAG_START = '{%';
570  const BLOCK_TAG_END = '%}';
571  const VARIABLE_TAG_START = '{{';
572  const VARIABLE_TAG_END = '}}';
573  const COMMENT_TAG_START = '{#';
574  const COMMENT_TAG_END = '#}';
575  const SINGLE_BRACE_START = '{';
576  const SINGLE_BRACE_END = '}';
577  // FIXME: ����������Ǥ������ѡ�����������äƤޤ�
578
579  function __construct($template) {
580    $this->template = $template;
581    $this->template_len = strlen($template);
582  }
583
584  // "��ǥ������Ȥ��줿������ƥ��ڡ�������� // "���"'������ˤϡ�\"\'�Ȥ��롣
585  // �ľ�����ɽ���ǽ񤱤Ф褫�ä������ޤ����ä���
586  // �ޥ��Х��ȥ����դʥǥ�������褦�˵��������� // $decode : quote����ǤΥ��������פ�ᤷ��������뤫�ɤ���
587  // $quote  : quoteʸ����������뤫�ɤ���
588  static public function smart_split($text, $delimiter = ' ', $decode = True, $quote = True) {
589    $epos = strlen($text);
590    $ret = array();
591    $mode = 'n'// 'n': not quoted, 'd': in ", 'q': in '
592    $buf = '';
593    for ($spos = 0; $spos < $epos; $spos++) {
594      $a = $text[$spos];
595      switch ($a) {
596        case '\\':
597          // �����art_split������$decode��se�ˤ��Ƥ���(ex. filter)
598          if (!$decode && $mode != 'n') {
599            $buf .= '\\';
600          }
601          switch ($mode) {
602            case 'd':
603              if ($text[$spos + 1] == '"') {
604                $buf .= '"';
605                $spos += 1;
606              } else if ($text[$spos + 1] == '\\') {
607                $buf .= '\\';
608                $spos += 1;
609              } else {
610                $buf .= $a;
611              }
612              break;
613            case 'q':
614              if ($text[$spos + 1] == "'") {
615                $buf .= "'";
616                $spos += 1;
617              } else if ($text[$spos + 1] == '\\') {
618                $buf .= '\\';
619                $spos += 1;
620              } else {
621                $buf .= $a;
622              }
623              break;
624            default: // 'n'
625              $buf.= '\\';
626          }
627          break;
628        case "'":
629          if ($quote) {
630            $buf .= "'";
631          }
632          switch ($mode) {
633            case 'd':
634              break;
635            case 'q':
636              $mode = 'n';
637              break;
638            default:
639              $mode = 'q';
640              break;
641          }
642          break;
643        case '"':
644          if ($quote) {
645            $buf .= '"';
646          }
647          switch ($mode) {
648            case 'd':
649              $mode = 'n';
650              break;
651            case 'q':
652              break;
653            default:
654              $mode = 'd';
655              break;
656          }
657          break;
658        case $delimiter:
659          switch ($mode) {
660            case 'd':
661            case 'q':
662              $buf .= $delimiter;
663              break;
664            default:
665              if ($buf != '') {
666                array_push($ret, $buf);
667                $buf = '';
668              }
669              break;
670          }
671          break;
672        default:
673          $buf .= $a;
674          break;
675      }
676    }
677    if ($mode == 'n' && $buf != '') {
678      array_push($ret, $buf);
679    }
680    return $ret;
681  }
682
683  // ��λ����(#}�Ȥ�)����ơ����ΰ�֤��
684  private function find_closetag($closetag) {
685    if (($fpos = strpos($this->template, $closetag, $this->spos)) === FALSE) {
686      return FALSE;
687    }
688    return $fpos;
689  }
690
691  private function make_errornode($errorCode) {
692    // from $spos to linenumber
693    // TODO: count on parsing
694    $c = substr($this->template, 0, $this->spos);
695    $ln = 1;
696    for ($i = 0; $i < $this->spos; $i++) {
697      switch ($c[$i]) {
698        case "\r":
699          if ($c[$i + 1] == "\n") {
700            $i++;
701          }
702          $ln++;
703          break;
704        case "\n":
705          $ln++;
706          break;
707      }
708    }
709    return new MuErrorNode($errorCode, $this->templatePath, $ln);
710  }
711
712  // {% %}�����������ơ�MuNode�����
713  // extends����˽񤫤ʤ��Ȥ����ʤ�
714  private function parse_block() {
715    $this->spos += 2;
716    if (($lpos = $this->find_closetag(self::BLOCK_TAG_END)) === FALSE) {
717      return $this->make_errornode('without_closetag_tag');
718    }
719    $in = $this->smart_split(substr($this->template, $this->spos, $lpos - $this->spos));
720    switch ($in[0]) {
721      // TODO: ������å����      case 'extends':
722        if ($this->extends !== FALSE) {
723          return $this->make_errornode('multiple_extends_tag');
724        }
725        if (count($in) != 2) {
726          return $this->make_errornode('numofparam_extends_tag');
727        }
728        $param = explode('"', $in[1]);
729        if (count($param) != 3) {
730          // Django��ѿ�K����ɤ�          return $this->make_errornode('invalidparam_extends_tag');
731        }
732        $this->extends = $param[1];
733        $this->spos = $lpos + 2;
734        break;
735      case 'include':
736        if (count($in) != 2) {
737          return $this->make_errornode('numofparam_include_tag');
738        }
739        $param = explode('"', $in[1]);
740        if (count($param) != 3) {
741          // Django��ѿ�K����ɤ�          return $this->make_errornode('invalidparam_include_tag');
742        }
743        $node = new MuIncludeNode($param[1]);
744        $this->spos = $lpos + 2;
745        break;
746      case 'block': // endblock
747        // TODO: check params
748        // TODO: filter block name
749        $blockname = $in[1];
750        if (array_key_exists($blockname, $this->block_dict)) {
751          return $this->make_errornode('multiple_block_tag');
752        }
753        $this->spos = $lpos + 2;
754        list($nodelist) = $this->_parse(array('endblock'));
755        $node = new MuBlockNode($blockname, $nodelist);
756        $this->block_dict[$blockname] = $node;
757        break;
758      case 'for': // endfor
759        // $in[1] = $loopvar, $in[2] = 'in', $in[3] = $sequence, $in[4] = 'reversed'
760        if ((count($in) != 4 && count($in) != 5) || $in[2] != 'in') {
761          return $this->make_errornode('numofparam_for_tag');
762        }
763        if (count($in) == 5) {
764          if ($in[4] == 'reversed') {
765            $reversed = True;
766          } else {
767            return $this->make_errornode('invalidparam_for_tag');
768          }
769        } else {
770          $reversed = False;
771        }
772        $this->spos = $lpos + 2;
773        list($nodelist) = $this->_parse(array('endfor'));
774        $node = new MuForNode($in[1], $in[3], $reversed, $nodelist);
775        break;
776      case 'cycle':
777        // TODO: implement namedCycleNodes
778        if (count($in) != 2) {
779          return $this->make_errornode('numofparam_cycle_tag');
780        }
781        $cyclevars = explode(',', $in[1]);
782        if (count($cyclevars) == 0) {
783          return $this->make_errornode('invalidparam_cycle_tag');
784        }
785        $node = new MuCycleNode($cyclevars);
786        $this->spos = $lpos + 2;
787        break;
788      case 'if': // else, endif
789        array_shift($in);
790        if (count($in) < 1) {
791          return $this->make_errornode('numofparam_if_tag');
792        }
793        $bitstr = implode(' ', $in);
794        $boolpairs = explode(' and ', $bitstr);
795        $boolvars = array();
796        if (count($boolpairs) == 1) {
797          $link_type = MuIfNode::LINKTYPE_OR;
798          $boolpairs = explode(' or ', $bitstr);
799        } else {
800          $link_type = MuIfNode::LINKTYPE_AND;
801          if (in_array(' or ', $bitstr)) {
802            return $this->make_errornode('andormixed_if_tag');
803          }
804        }
805        foreach ($boolpairs as $boolpair) {
806          // handle 'not'
807          if (strpos($boolpair, ' ') !== FALSE) {
808            // TODO: error handling
809            list($not, $boolvar) = explode(' ', $boolpair);
810            if ($not != 'not') {
811              return $this->make_errornode('invalidparam_if_tag');
812            }
813            array_push($boolvars, array(True, $boolvar));
814          } else {
815            array_push($boolvars, array(False, $boolpair));
816          }
817        }
818        $this->spos = $lpos + 2;
819        list($nodelist_true, $nexttag) =
820          $this->_parse(array('else', 'endif'));
821        if ($nexttag == 'else') {
822          list($nodelist_false) = $this->_parse(array('endif'));
823        } else {
824          $nodelist_false = new MuNodeList();
825        }
826        $node = new MuIfNode($boolvars, $nodelist_true, $nodelist_false, $link_type);
827        break;
828      case 'debug':
829        $node = new MuDebugNode();
830        $this->spos = $lpos + 2;
831        break;
832      case 'now':
833        if (count($in) != 2) {
834          return $this->make_errornode('numofparam_now_tag');
835        }
836        $param = explode('"', $in[1]);
837        if (count($param) != 3) {
838          return $this->make_errornode('invalidparam_now_tag');
839        }
840        $node = new MuNowNode($param[1]);
841        $this->spos = $lpos + 2;
842        break;
843      case 'filter': // endfilter
844        if (count($in) != 2) {
845          return $this->make_errornode('numofparam_filter_tag');
846        }
847        $this->spos = $lpos + 2;
848        if (($filter_expr = new MuFilterExpression('var|'. $in[1])) === FALSE) {
849          return $this->make_errornode('invalidparam_filter_tag');
850        }
851        list($nodelist) = $this->_parse(array('endfilter'));
852        $node = new MuFilterNode($filter_expr, $nodelist);
853        break;
854      case 'endblock':
855      case 'else':
856      case 'endif':
857      case 'endfor':
858      case 'endfilter':
859        $node = $in[0]; // raw string
860        $this->spos = $lpos + 2;
861        break;
862      default:
863        $node = $this->make_errornode('unknown_tag');
864        $this->spos = $lpos + 2;
865        break;
866    }
867    return $node;
868  }
869
870  private function parse_variable() {
871    $this->spos += 2;
872    if (($lpos = $this->find_closetag(self::VARIABLE_TAG_END)) === FALSE) {
873      return FALSE;
874    }
875    // TODO: handle empty {{ }}
876    if (($fil = new MuFilterExpression(
877                  substr($this->template, $this->spos, $lpos - $this->spos))) === FALSE) {
878      return $this->make_errornode('invalidparam_filter_variable');
879    }
880    $node = new MuVariableNode($fil);
881    $this->spos = $lpos + 2;
882    return $node;
883  }
884
885  private function parse_comment() {
886    $this->spos += 2;
887    if (($lpos = $this->find_closetag(self::COMMENT_TAG_END)) === FALSE) {
888      return FALSE;
889    }
890    $this->spos = $lpos + 2;
891  }
892
893  static public function parse_from_file($templatePath) {
894    if (($t = file_get_contents($templatePath)) === FALSE) {
895      return FALSE;
896    }
897    $p = new MuParser($t);
898    $p->templatePath = $templatePath;
899    list($nl) = $p->_parse(array());
900    return new MuFile($nl, $p->block_dict, $p->extends, $templatePath);
901  }
902
903  static public function parse($templateStr) {
904    $p = new MuParser($templateStr);
905    list($nl) = $p->_parse(array());
906    return new MuFile($nl, $p->block_dict);
907  }
908
909  private function add_textnode($nodelist, $tspos, $epos) {
910    if ($tspos < $epos) {
911      $nodelist->push(new MuTextNode(
912        substr($this->template, $tspos, $epos - $tspos)));
913    }
914  }
915
916  private function _parse($parse_until) {
917    $nl = new MuNodeList();
918    $tspos = $this->spos;
919    while (true) {
920      $this->spos = strpos($this->template, self::SINGLE_BRACE_START, $this->spos);
921      if ($this->spos === FALSE) {
922        $this->spos = $this->template_len;
923        $this->add_textnode($nl, $tspos, $this->template_len);
924        return array($nl, null);
925      }
926      switch (substr($this->template, $this->spos, 2)) {
927        case self::BLOCK_TAG_START:
928          $this->add_textnode($nl, $tspos, $this->spos);
929          if (($node = $this->parse_block()) === FALSE) {
930            return FALSE;
931          }
932          $tspos = $this->spos;
933          if (is_string($node)) {
934            // close tag
935            if (in_array($node, $parse_until)) {
936              return array($nl, $node);
937            } else {
938              // invalid close tag
939            }
940          } else {
941            $nl->push($node);
942          }
943          break;
944        case self::VARIABLE_TAG_START:
945          $this->add_textnode($nl, $tspos, $this->spos);
946          if (($node = $this->parse_variable()) === FALSE) {
947            return FALSE;
948          }
949          $nl->push($node);
950          $tspos = $this->spos;
951          break;
952        case self::COMMENT_TAG_START:
953          $this->add_textnode($nl, $tspos, $this->spos);
954          if (($node = $this->parse_comment()) === FALSE) {
955            return FALSE;
956          }
957          $nl->push($node);
958          $tspos = $this->spos;
959          break;
960        default:
961          // { only
962          $this->spos += 1;
963      }
964    }
965  }
966}
967
968class MuErrorHandler {
969
970  private static $errorType = array (
971    E_ERROR             => 'ERROR',
972    E_WARNING           => 'WARNING',
973    E_PARSE             => 'PARSING ERROR',
974    E_NOTICE            => 'NOTICE',
975    E_CORE_ERROR        => 'CORE ERROR',
976    E_CORE_WARNING      => 'CORE WARNING',
977    E_COMPILE_ERROR     => 'COMPILE ERROR',
978    E_COMPILE_WARNING   => 'COMPILE WARNING',
979    E_USER_ERROR        => 'USER ERROR',
980    E_USER_WARNING      => 'USER WARNING',
981    E_USER_NOTICE       => 'USER NOTICE',
982    E_STRICT            => 'STRICT NOTICE',
983    E_RECOVERABLE_ERROR => 'RECOVERABLE ERROR'
984  );
985
986  // error page html
987  const BACKTRACE_HTML = '
988<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
989<html lang="en">
990<head>
991  <meta http-equiv="content-type" content="text/html; charset=utf-8" />
992  <meta name="robots" content="NONE,NOARCHIVE" />
993  <title>{{ error_type }}</title>
994  <style type="text/css">
995    html * { padding:0; margin:0; }
996    body * { padding:10px 20px; }
997    body * * { padding:0; }
998    body { font:small sans-serif; }
999    body>div { border-bottom:1px solid #ddd; }
1000    h1 { font-weight:normal; }
1001    h2 { margin-bottom:.8em; }
1002    h2 span { font-size:80%; color:#666; font-weight:normal; }
1003    h3 { margin:1em 0 .5em 0; }
1004    h4 { margin:0 0 .5em 0; font-weight: normal; }
1005    table { border:1px solid #ccc; border-collapse: collapse; width:100%; background:white; }
1006    tbody td, tbody th { vertical-align:top; padding:2px 3px; }
1007    thead th { padding:1px 6px 1px 3px; background:#fefefe; text-align:left; font-weight:normal; font-size:11px; border:1px solid #ddd; }
1008    tbody th { width:12em; text-align:right; color:#666; padding-right:.5em; }
1009    table.vars { margin:5px 0 2px 40px; }
1010    table.vars td, table.req td { font-family:monospace; }
1011    table td.code { width:100%; }
1012    table td.code div { overflow:hidden; }
1013    table.source th { color:#666; }
1014    table.source td { font-family:monospace; white-space:pre; border-bottom:1px solid #eee; }
1015    ul.traceback { list-style-type:none; }
1016    ul.traceback li.frame { margin-bottom:1em; }
1017    div.context { margin: 10px 0; }
1018    div.context ol { padding-left:30px; margin:0 10px; list-style-position: inside; }
1019    div.context ol li { font-family:monospace; white-space:pre; color:#666; cursor:pointer; }
1020    div.context ol.context-line li { color:black; background-color:#ccc; }
1021    div.context ol.context-line li span { float: right; }
1022    div.commands { margin-left: 40px; }
1023    div.commands a { color:black; text-decoration:none; }
1024    #summary { background: #ffc; }
1025    #summary h2 { font-weight: normal; color: #666; }
1026    #explanation { background:#eee; }
1027    #template, #template-not-exist { background:#f6f6f6; }
1028    #template-not-exist ul { margin: 0 0 0 20px; }
1029    #traceback { background:#eee; }
1030    #requestinfo { background:#f6f6f6; padding-left:120px; }
1031    #summary table { border:none; background:transparent; }
1032    #requestinfo h2, #requestinfo h3 { position:relative; margin-left:-100px; }
1033    #requestinfo h3 { margin-bottom:-1em; }
1034    .error { background: #ffc; }
1035    .specific { color:#cc3300; font-weight:bold; }
1036  </style>
1037  <script type="text/javascript">
1038  //<!--
1039    function getElementsByClassName(oElm, strTagName, strClassName){
1040        // Written by Jonathan Snook, http://www.snook.ca/jon; Add-ons by Robert Nyman, http://www.robertnyman.com
1041        var arrElements = (strTagName == "*" && document.all)? document.all :
1042        oElm.getElementsByTagName(strTagName);
1043        var arrReturnElements = new Array();
1044        strClassName = strClassName.replace(/\-/g, "\-");
1045        var oRegExp = new RegExp("(^|\s)" + strClassName + "(\s|$)");
1046        var oElement;
1047        for(var i=0; i<arrElements.length; i++){
1048            oElement = arrElements[i];
1049            if(oRegExp.test(oElement.className)){
1050                arrReturnElements.push(oElement);
1051            }
1052        }
1053        return (arrReturnElements)
1054    }
1055    function hideAll(elems) {
1056      for (var e = 0; e < elems.length; e++) {
1057        elems[e].style.display = \'none\';
1058      }
1059    }
1060    window.onload = function() {
1061      hideAll(getElementsByClassName(document, \'table\', \'vars\'));
1062      hideAll(getElementsByClassName(document, \'ol\', \'pre-context\'));
1063      hideAll(getElementsByClassName(document, \'ol\', \'post-context\'));
1064      hideAll(getElementsByClassName(document, \'div\', \'pastebin\'));
1065    }
1066    function toggle() {
1067      for (var i = 0; i < arguments.length; i++) {
1068        var e = document.getElementById(arguments[i]);
1069        if (e) {
1070          e.style.display = e.style.display == \'none\' ? \'block\' : \'none\';
1071        }
1072      }
1073      return false;
1074    }
1075    function varToggle(link, id) {
1076      toggle(\'v\' + id);
1077      var s = link.getElementsByTagName(\'span\')[0];
1078      var uarr = String.fromCharCode(0x25b6);
1079      var darr = String.fromCharCode(0x25bc);
1080      s.innerHTML = s.innerHTML == uarr ? darr : uarr;
1081      return false;
1082    }
1083    function switchPastebinFriendly(link) {
1084      s1 = "Switch to copy-and-paste view";
1085      s2 = "Switch back to interactive view";
1086      link.innerHTML = link.innerHTML == s1 ? s2 : s1;
1087      toggle(\'browserTraceback\', \'pastebinTraceback\');
1088      return false;
1089    }
1090    //-->
1091  </script>
1092</head>
1093<body>
1094<div id="summary">
1095  <h1>{{ error_type|escape }}</h1>
1096  <h2>{{ error_str|escape }}</h2>
1097  <table class="meta">
1098    <!--tr>
1099      <th>Request Method:</th>
1100      <td>{{ request.method|escape }}</td>
1101    </tr>
1102    <tr>
1103      <th>Request URL:</th>
1104      <td>{{ request.url|escape }}</td>
1105    </tr>
1106    <tr>
1107      <th>Exception Type:</th>
1108      <td>{{ exception.type }}</td>
1109    </tr-->
1110    <tr>
1111      <th>Exception Value:</th>
1112      <td>{{ error_str }}</td>
1113    </tr>
1114    <tr>
1115      <th>Exception Location:</th>
1116      <td>{{ error_file|escape }}, line {{ error_line }}</td>
1117      <!-- TODO: get function name -->
1118    </tr>
1119  </table>
1120</div>
1121
1122
1123</body>
1124';
1125
1126  static public function getArguments(&$args)
1127  {
1128    $pargs = array();
1129    foreach($args as $arg) {
1130      array_push($pargs, self::getArgument($arg));
1131    }
1132    return $pargs;
1133  }
1134
1135  static public function getArgument($arg, $recursion = true)
1136  {
1137    switch (strtolower(gettype($arg))) {
1138      case 'string':
1139        return '"'.$arg.'"';
1140      case 'boolean':
1141        return (bool)$arg;
1142      case 'object':
1143        return 'object('. get_class($arg) . ')';
1144      case 'array':
1145        $pargs = array();
1146        foreach ($arg as $key => $value) {
1147          if (!$recursion) {
1148            array_push($pargs, self::getArgument($key, false). ' => '. self::getArgument($value, false));
1149          }
1150        }
1151        return 'array('. implode(', ', $pargs) . ')';
1152      case 'resource':
1153        return 'resource('. get_resource_type($arg). ')';
1154      default:
1155        return var_export($arg, true);
1156    }
1157  }
1158
1159  static public function handler($errno, $errstr = '', $errfile = '', $errline = '', $errcontext = null)
1160  {
1161    $p = array('backtrace' => array());
1162    if (error_reporting() == 0) {
1163      return;
1164    }
1165    if (func_num_args() == 5) {
1166      // error
1167      list($p['errno'], $p['error_str'], $p['error_file'], $p['error_line']) = func_get_args();
1168      $backtrace = array_reverse(debug_backtrace());
1169    } else {
1170      // exception
1171      $exc = func_get_arg(0);
1172      $p['errno'] = $exc->getCode();
1173      $p['error_str'] = $exc->getMessage();
1174      $p['error_file'] = $exc->getFile();
1175      $p['error_line'] = $exc->getLine();
1176      $backtrace = $exc->getTrace();
1177    }
1178    array_pop($backtrace); // remove handler self
1179    if (array_key_exists($errno, self::$errorType)) {
1180      $p['error_type'] = self::$errorType[$p['errno']];
1181    } else {
1182      $p['error_type'] = 'CAUGHT EXCEPTION';
1183    }
1184    foreach ($backtrace as $bt) {
1185      if (isset($bt['class'])) {
1186        $trace = 'in class '. $bt['class'].'::'.$v['function'].'(';
1187        if (isset($bt['args'])) {
1188          $trace .= implode(', ', self::getArguments($bt['args']));
1189        }
1190        $trace .= ')';
1191        array_push($p['backtrace'], $trace);
1192      } elseif (isset($bt['function'])) {
1193        $trace = 'in function '.$bt['function'].'(';
1194        if (isset($bt['args'])) {
1195          $trace .= implode(', ', self::getArguments($bt['args']));
1196        }
1197        $trace .= ')';
1198        array_push($p['backtrace'], $trace);
1199      } else {
1200        array_push($p['backtrace'], 'unknown');
1201      }
1202    }
1203    switch ($p['errno']) {
1204      case E_NOTICE:
1205      case E_USER_NOTICE:
1206      case E_STRICT:
1207        return;
1208        break;
1209      default:
1210        echo MuParser::parse(self::BACKTRACE_HTML)->render($p);
1211    }
1212    exit(1);
1213  }
1214}
1215?>
Note: See TracBrowser for help on using the browser.