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

Revision 2574, 40.9 kB (checked in by tasuku, 5 years ago)

r302@dhcp158 (orig r45): tasuku | 2007-09-11 22:33:10 +0900
first of jissou


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