root/lang/php/mumu/trunk/GunyaTemplate.php @ 2551

Revision 2551, 27.3 kB (checked in by tasuku, 7 years ago)

r279@dhcp158 (orig r22): tasuku | 2007-09-07 15:16:19 +0900
block.super tokano instance method ga yoberu youni!


Line 
1<?php
2// GunyaTemplate (c) Brazil, Inc.
3// originally developed by Tasuku SUENAGA a.k.a. gunyarakun
4// developed from 2007/09/04
5
6// �����
7// - ���Υե�����������OK
8// - ��
9// - �������嵡ǽ
10// - �إå���եå���nclude
11// - �ƥ�졼����롼�פ�������/ - �ƥ�졼�ȥե�����ľ��tml�Ȥ��Ʊ���
12// - url���󥳡��ɤ�mlspecialchars����˻������/ - PHP5��׵᤹��
13
14// �Ȥ���γ��(����)
15// - ���٤ƤΥǡ�����å�������ʻ����ǡ������ʤ��Ȥ����ʤ���// - �ƥ�졼�ȥե�������ꤹ��/ - �������������̵ͭ����Ǥ���// - �����줿������äƤ���/ - echo�ʤ���ꤪ������
16// ��
17// ��� {{ ���}}             : ����ִ�
18
19// �֥���
20// {% include "filename" %} : �ƥ�졼�ȤΥ��󥯥롼��// {% extends "filename" %} : �ƥ�졼�Ȥγ��
21// {% block blockname %}    : �֥����λϤޤ�/ {% endblock %}           : �֥����ν���
22// {% for item in items %}  : ���ms����em�����
23// {% endfor %}             : for�ν���
24// {% cycle val1,val2 %}    : for�롼�פ��val1,val2��ߤ˽Ф�(ɽ�ǹԤ���طʿ��Ȥ�)
25// {% if cond %} {% else %} : cond���������줿�Ȥ��������// {% endif %}              : if�ν���
26// {% debug %}              : �ƥ�졼�Ȥ�Ϥ��줿�������/ {% now "format" %}       : ���ߤ���������ޥåȤǽ�Ϥ��ޤ�
27// {% filter fil1|fil2 %}   : �֥������������륿�ˤ����ޤ�
28// {% endfilter %}          : filter�ν���
29// {# comment #}            : ������
30// �ѥ���// {{ ����ѥ���|�ѥ��� }} : ���ե��륿���ƽ�Ϥ���/ |addslashes                  : \��ˡ�JavaScript���Ϥ�����������
31// |length                      : �������
32// |escape                      : %<>"'�Υ���������// |stringformat:"format"       : ��ꤷ���ե����ޥåȤ�ͤ�����ޥå�// |urlencode                   : url���󥳡���// |linebreaksbr                : ���Ԥ� />��Ѵ�
33
34// ������ forloop.counter     : ���ߤΥ롼�ײ�ֹ�1 ����������)
35// forloop.counter0    : ���ߤΥ롼�ײ�ֹ�0 ����������)
36// forloop.revcounter  : �����������롼�ײ�ֹ�1 ����������)
37// forloop.revcounter0 : �����������롼�ײ�ֹ�0 ����������)
38// forloop.first       : �ǽ��롼�פǤ��� true �ˤʤ���
39// forloop.last        : �Ǹ��롼�פǤ��� true �ˤʤ���
40// forloop.parentloop  : ����Υ롼�פξ��������롼�פ���ޤ�
41// block.super         : �ƥƥ�졼�Ȥ�lock��������������ä���������
42
43// ��
44// find_endtags�ϸ��Ĥ����parse�⤷�ơ�$sposư�����Ƥ⤤���󤸤�����
45
46class GTContext {
47  // �ƥ�졼�Ȥ��Ϥ���ξ�����饹
48  private $dicts;
49  const VARIABLE_ATTRIBUTE_SEPARATOR = '.';
50  function __construct($dict = array()) {
51    $this->dicts = array($dict);
52  }
53  // �ɥå���������������
54  function resolve($expr) {
55    $bits = explode(self::VARIABLE_ATTRIBUTE_SEPARATOR, $expr);
56    $current = $this->get($bits[0]);
57    array_shift($bits);
58    while ($bits) {
59      if (is_array($current) && array_key_exists($bits[0], $current)) {
60        // array���������(�������⥳����)
61        $current = $current[$bits[0]];
62      } elseif (method_exists($current, $bits[0])) {
63        // �᥽�åɥ�����       if (($current = call_user_func(array($current, $bits[0]))) === FALSE) {
64          return 'method call error';
65        }
66      } else {
67        return 'resolve error';
68      }
69      array_shift($bits);
70    }
71    return $current;
72  }
73  function has_key($key) {
74    foreach ($this->dicts as $dict) {
75      if (array_key_exists($key, $dict)) {
76        return True;
77      }
78    }
79    return False;
80  }
81  function get($key) {
82    foreach ($this->dicts as $dict) {
83      if (array_key_exists($key, $dict)) {
84        return $dict[$key];
85      }
86    }
87    return null;
88  }
89  function set($key, $value) {
90    $this->dicts[0][$key] = $value;
91  }
92  function push() {
93    array_unshift($this->dicts, array());
94  }
95  function pop() {
96    array_shift($this->dicts);
97  }
98  function update($other_array) {
99    array_unshift($this->dicts, $other_array);
100  }
101}
102
103class GTNode {
104  public function _render() {
105    return '';
106  }
107}
108
109class GTNodeList {
110  private $nodes;
111  function __construct() {
112    $this->nodes = array();
113  }
114  public function _render($context) {
115    $bits = array();
116    foreach ($this->nodes as $node) {
117      array_push($bits, $node->_render($context));
118    }
119    return implode('', $bits);
120  }
121  public function push($node) {
122    array_push($this->nodes, $node);
123  }
124}
125
126// 1�ĤΥƥ�졼�ȥե������ѡ�����������
127// ��������˽Ф���������class GTFile extends GTNode {
128  public $nodelist;        // �ե������ѡ�������NodeList
129  private $block_dict;      // nodelist�������ock̾ => GTBlockNode(�λ���
130  private $parent_tfile;    // extends���������ƥƥ�졼��  function __construct($nodelist, $block_dict, $parentPath = false) {
131    $this->nodelist = $nodelist;
132    $this->block_dict = $block_dict;
133    if ($parentPath) {
134      $p = new GTParser();
135      if (($this->parent_tfile = $p->parse_from_file($parentPath)) === FALSE) {
136        // TODO: ���顼���������ƥ�졼�������˶����Ƥ�����     }
137    }
138  }
139  public function render($raw_context) {
140    return $this->_render(new GTContext($raw_context));
141  }
142  public function _render($context) {
143    if ($this->parent_tfile) {
144      foreach ($this->block_dict as $blockname => $blocknode) {
145        if (($parent_block = $this->parent_tfile->get_block($blockname)) === FALSE) {
146          if ($this->parent_tfile->is_child()) {
147            $this->parent_tfile->append_block(&$blocknode);
148          }
149        } else {
150          $parent_block->parent = &$blocknode->parent;
151          $parent_block->add_parent(&$parent_block->nodelist);
152          $parent_block->nodelist = &$blocknode->nodelist;
153        }
154      }
155      return $this->parent_tfile->_render($context);
156    } else {
157      return $this->nodelist->_render($context);
158    }
159  }
160  public function get_block($blockname) {
161    if (array_key_exists($blockname, $this->block_dict)) {
162      return $this->block_dict[$blockname];
163    } else {
164      return false;
165    }
166  }
167  public function append_block($blocknode) {
168    // ¹������줿�֥���̾�ǡ��Ƥˤ���������ʤ�����
169    // �ƤοƤˤ���������������ᡢ
170    // ¹�����˥֥������
171    $this->nodelist->push($blocknode);
172    $this->block_dict[$blocknode->name] = &$blocknode;
173  }
174  public function is_child() {
175    return ($parent_tfile !== FALSE);
176  }
177}
178
179class GTTextNode extends GTNode {
180  private $text;
181  function __construct($text) {
182    $this->text = $text;
183  }
184  public function _render($context) {
185    return $this->text;
186  }
187}
188
189class GTVariableNode extends GTNode {
190  private $filter_expression;
191  function __construct($filter_expression) {
192    $this->filter_expression = $filter_expression;
193  }
194  public function _render($context) {
195    return $this->filter_expression->resolve($context);
196  }
197}
198
199class GTIncludeNode extends GTNode {
200  private $tplfile;
201  function __construct($includePath) {
202    // FIXME: �������ƥ�����å���̵�¥롼�ץ���å�
203    $p = new GTParser();
204    if (($this->tplfile = $p->parse_from_file($includePath)) === FALSE) {
205      // TODO: ���顼���������ƥ�졼�������˶����Ƥ�����     $this->tplfile = new GTTextNode('include error');
206    }
207  }
208  public function _render($context) {
209    return $this->tplfile->_render($context);
210  }
211}
212
213class GTBlockNode extends GTNode {
214  public $name;
215  public $nodelist;
216  public $parent;
217
218  private $context; // for block.super
219
220  function __construct($name, $nodelist, $parent = Null) {
221    $this->name = $name;
222    $this->nodelist = $nodelist;
223    $this->parent = $parent;
224  }
225  public function _render($context) {
226    $context->push();
227    $this->context = &$context;
228    $context->set('block', $this); // block.super�
229    $res = $this->nodelist->_render($context);
230    $context->pop();
231    return $res;
232  }
233  public function super() {
234    if ($this->parent) {
235      return $this->parent->_render($this->context);
236    }
237    return '';
238  }
239  public function add_parent($nodelist) {
240    if ($this->parent) {
241      $this->parent->add_parent($nodelist);
242    } else {
243      $this->parent = new GTBlockNode($this->name, $this->nodelist);
244    }
245  }
246}
247
248class GTCycleNode extends GTNode {
249  private $cyclevars;
250  private $cyclevars_len;
251  private $variable_name;
252
253  function __construct($cyclevars, $variable_name = null) {
254    $this->cyclevars = $cyclevars;
255    $this->cyclevars_len = count($cyclevars);
256    $this->counter = -1;
257    $this->variable_name = $variable_name;
258  }
259
260  function _render($context) {
261    $this->counter++;
262    $value = $this->cyclevars[$this->counter % $this->cyclevars_len];
263    if ($this->variable_name) {
264      $context.set($this->variable_name, $value);
265    }
266    return $value;
267  }
268}
269
270class GTDebugNode extends GTNode {
271  function _render($context) {
272    ob_start();
273    var_dump($context);
274    $output = ob_get_contents();
275    ob_end_clean();
276    return $output;
277  }
278}
279
280class GTFilterNode extends GTNode {
281  private $filter_expr;
282  private $nodelist;
283  function __construct($filter_expr, $nodelist) {
284    $this->filter_expr = $filter_expr;
285    $this->nodelist = $nodelist;
286  }
287  function _render($context) {
288    $output = $this->nodelist->_render($context);
289    $context->update(array('var' => $output));
290    $filtered = $this->filter_expr->resolve($context);
291    $context->pop();
292    return $filtered;
293  }
294}
295
296class GTForNode extends GTNode {
297  private $loopvar;
298  private $sequence;
299  private $reversed;
300  private $nodelist_loop;
301
302  function __construct($loopvar, $sequence, $reversed, $nodelist_loop) {
303    $this->loopvar = $loopvar;
304    $this->sequence = $sequence;
305    $this->reversed = $reversed;
306    $this->nodelist_loop = $nodelist_loop;
307  }
308  function _render($context) {
309    if ($context->has_key('forloop')) {
310      $parentloop = $context->get('forloop');
311    } else {
312      $parentloop = new GTContext();
313    }
314    $context->push();
315    if (!($values = $context->resolve($this->sequence))) {
316      $values = array();
317    }
318    if (!is_array($values)) {
319      $values = array($value);
320    }
321    $len_values = count($values);
322    // FIXME: $this->reversed
323    $rnodelist = array();
324    for ($i = 0; $i < $len_values; $i++) {
325      $context->set('forloop', array(
326        'counter0' => $i,
327        'counter' => $i + 1,
328        'revcounter' => $len_values - $i,
329        'revcounter0' => $len_values - $i - 1,
330        'first' => ($i == 0),
331        'last' => ($i == ($len_values - 1)),
332        'parentloop' => $parentloop
333      ));
334      $context->set($this->loopvar, $values[$i]);
335      array_push($rnodelist, $this->nodelist_loop->_render($context));
336    }
337    $context->pop();
338    return implode('', $rnodelist);
339  }
340}
341
342class GTIfNode extends GTNode {
343  private $bool_exprs;
344  private $nodelist_true;
345  private $nodelist_false;
346  private $link_type;
347
348  const LINKTYPE_AND = 0;
349  const LINKTYPE_OR  = 1;
350
351  function __construct($bool_exprs, $nodelist_true, $nodelist_false, $link_type) {
352    $this->bool_exprs = $bool_exprs;
353    $this->nodelist_true = $nodelist_true;
354    $this->nodelist_false = $nodelist_false;
355    $this->link_type = $link_type;
356  }
357  function _render($context) {
358    if ($this->link_type == self::LINKTYPE_OR) {
359      foreach ($this->bool_exprs as $be) {
360        list($ifnot, $bool_expr) = $be;
361        $value = $context->resolve($bool_expr);
362        if (($value && !$ifnot) || ($ifnot && !$value)) {
363          return $this->nodelist_true->_render($context);
364        }
365      }
366      return $this->nodelist_false->_render($context);
367    } else { // self::LINKTYPE_AND
368      foreach ($this->bool_exprs as $be) {
369        list($ifnot, $bool_expr) = $be;
370        $value = $context->resolve($bool_expr);
371        if (!(($value && !$ifnot) || ($ifnot && !$value))) {
372          return $this->nodelist_false->_render($context);
373        }
374      }
375      return $this->nodelist_true->_render($context);
376    }
377  }
378}
379
380class GTNowNode extends GTNode {
381  private $format_string;
382  function __construct($format_string) {
383    $this->format_string = $format_string;
384  }
385  public function _render($context) {
386    return date($this->format_string);
387  }
388}
389
390class GTUnknownNode extends GTNode {
391  public function _render($context) {
392    return 'unknown...';
393  }
394}
395
396class GTFilterExpression {
397  private $var;
398  private $filters;
399
400  function __construct($token) {
401    // $token = 'variable|default:"Default value"|date:"Y-m-d"'
402    // �äƤΤ����ä��顢
403    // $this->var = 'variable'
404    // $this->filters = 'array(array('default, 'Default value'), array('date', 'Y-m-d'))'
405    // �äƤ��롣
406    // Django�Τ��ǻϤޤä��餤���ʤ��餷����
407
408    $fils = GTParser::smart_split(trim($token), '|', False, True);
409    $this->var = array_shift($fils);
410    $this->filters = array();
411    foreach ($fils as $fil) {
412      $f = GTParser::smart_split($fil, ':', True, False);
413      array_push($this->filters, $f);
414    }
415  }
416
417  // TODO: support ignore_failures
418  public function resolve($context) {
419    // eval�Ȥ�call_user_func_array������witch-case��ispatch�����ɤ�����    $val = $context->resolve($this->var);
420    foreach ($this->filters as $fil) {
421      // TODO: �����å�
422      switch ($fil[0]) {
423        case 'addslashes':
424          $val = addslashes($val);
425          break;
426        case 'length':
427          # array��ount��string��trlen
428          if (is_array($val)) {
429            $val = count($val);
430          } else if (is_string($val)) {
431            $val = strlen($val);
432          }
433          break;
434        case 'escape':
435          $val = htmlspecialchars($val);
436          break;
437        case 'stringformat':
438          // $fil[1]�˥���ʸ���ʤ��褦�˵���������         $val = sprintf($fil[1], $val);
439          break;
440        case 'urlencode':
441          $val = urlencode($val);
442          break;
443        case 'linebreaksbr':
444          $val = nl2br($val);
445          break;
446        default:
447          // �ɤ�ե��륿̾���ޥ����ä��������Ƥ����������ɡ�
448          // �������������äƤ��ޥ���������ڤ�
449          // TODO: �ե��륿̾�򥢥����٥åȤ��Ȥ��Τߤ˥ե��륿���Ƥ���
450          $val = 'unknown filter specified';
451      }
452    }
453    return $val;
454  }
455}
456
457class GTParser {
458  private $template;             // �ѡ�����Υƥ�졼����� private $errorStr;             // ���顼ʸ�� private $block_dict = array(); // block���� => block�ؤλ���  private $extends = false;      // extends�ξ��Υե�����
459
460  # template syntax constants
461  const FILTER_SEPARATOR = '|';
462  const FILTER_ARGUMENT_SEPARATOR = ':';
463  const VARIABLE_ATTRIBUTE_SEPARATOR = '.';
464  const BLOCK_TAG_START = '{%';
465  const BLOCK_TAG_END = '%}';
466  const VARIABLE_TAG_START = '{{';
467  const VARIABLE_TAG_END = '}}';
468  const COMMENT_TAG_START = '{#';
469  const COMMENT_TAG_END = '#}';
470  const SINGLE_BRACE_START = '{';
471  const SINGLE_BRACE_END = '}';
472  // FIXME: ����������Ǥ������ѡ�����������äƤޤ�
473
474  // "��ǥ������Ȥ��줿������ƥ��ڡ�������� // "���"'������ˤϡ�\"\'�Ȥ��롣
475  // �ľ�����ɽ���ǽ񤱤Ф褫�ä������ޤ����ä���
476  // �ޥ��Х��ȥ����դʥǥ�������褦�˵���������  // $decode : quote����ǤΥ��������פ�ᤷ��������뤫�ɤ���
477  // $quote  : quoteʸ����������뤫�ɤ���
478  static public function smart_split($text, $delimiter = ' ', $decode = True, $quote = True) {
479    $epos = strlen($text);
480    $ret = array();
481    $mode = 'n'// 'n': not quoted, 'd': in ", 'q': in '
482    for ($spos = 0; $spos < $epos; $spos++) {
483      $a = $text[$spos];
484      switch ($a) {
485        case '\\':
486          // �����art_split������$decode��se�ˤ��Ƥ���(ex. filter)
487          if (!$decode && $mode != 'n') {
488            $buf .= '\\';
489          }
490          switch ($mode) {
491            case 'd':
492              if ($text[$spos + 1] == '"') {
493                $buf .= '"';
494                $spos += 1;
495              } else if ($text[$spos + 1] == '\\') {
496                $buf .= '\\';
497                $spos += 1;
498              } else {
499                $buf .= $a;
500              }
501              break;
502            case 'q':
503              if ($text[$spos + 1] == "'") {
504                $buf .= "'";
505                $spos += 1;
506              } else if ($text[$spos + 1] == '\\') {
507                $buf .= '\\';
508                $spos += 1;
509              } else {
510                $buf .= $a;
511              }
512              break;
513            default: // 'n'
514              $buf.= '\\';
515          }
516          break;
517        case "'":
518          if ($quote) {
519            $buf .= "'";
520          }
521          switch ($mode) {
522            case 'd':
523              break;
524            case 'q':
525              $mode = 'n';
526              break;
527            default:
528              $mode = 'q';
529              break;
530          }
531          break;
532        case '"':
533          if ($quote) {
534            $buf .= '"';
535          }
536          switch ($mode) {
537            case 'd':
538              $mode = 'n';
539              break;
540            case 'q':
541              break;
542            default:
543              $mode = 'd';
544              break;
545          }
546          break;
547        case $delimiter:
548          switch ($mode) {
549            case 'd':
550            case 'q':
551              $buf .= $delimiter;
552              break;
553            default:
554              if ($buf != '') {
555                array_push($ret, $buf);
556                $buf = '';
557              }
558              break;
559          }
560          break;
561        default:
562          $buf .= $a;
563          break;
564      }
565    }
566    if ($mode == 'n' && $buf != '') {
567      array_push($ret, $buf);
568    }
569    return $ret;
570  }
571
572  // ��λ����(#}�Ȥ�)����ơ����ΰ�֤��
573  private function find_closetag(&$spos, &$epos, $closetag) {
574    if (($fpos = strpos($this->template, $closetag, $spos)) === FALSE || $fpos >= $epos) {
575      $this->errorStr = "������������Ƥʤ��褦�Ǥ�($closetag�����Ĥ�������";
576      return FALSE;
577    }
578    return $fpos;
579  }
580
581  // ľ��else��dblock��dif��dfor���
582  private function find_endtags($spos, &$epos, $endtags) {
583    // �ޤ���ľ���֥������ϥ��������    while (($spos = strpos($this->template, self::BLOCK_TAG_START, $spos)) !== FALSE
584           && $spos < $epos) {
585      $spos += 2;
586      if (($lpos = $this->find_closetag($spos, $epos, self::BLOCK_TAG_END)) !== FALSE
587          && $lpos < $epos) {
588        // �������m���ƥ���å�
589        $c = trim(substr($this->template, $spos, $lpos - $spos));
590        if (in_array($c, $endtags)) {
591          return array($spos - 2, $lpos + 2, $c);
592        }
593        $spos = $lpos + 2;
594      }
595    }
596    $this->errorStr = "block/if/for��������Ƥ��ʤ��褦�Ǥ���";
597    return FALSE;
598  }
599
600  // {% %}�����������ơ�GTNode�����
601  // extends����˽񤫤ʤ��Ȥ����ʤ�
602  private function parse_block(&$spos, &$epos) {
603    $spos += 2;
604    if (($lpos = $this->find_closetag($spos, $epos, self::BLOCK_TAG_END)) === FALSE) {
605      return FALSE;
606    }
607    $in = $this->smart_split(substr($this->template, $spos, $lpos - $spos));
608    switch ($in[0]) {
609      // TODO: ������å����      case 'extends':
610        if ($this->extends !== FALSE) {
611          $this->errorStr = 'extends�ϣ��Ĥ������������ޤ���;
612          return FALSE;
613        }
614        if (count($in) != 2) {
615          $this->errorStr = 'extends�Υѥ�������ꤷ�Ƥ������';
616          return FALSE;
617        }
618        $param = explode('"', $in[1]);
619        if (count($param) != 3) {
620          // Django��ѿ�K����ɤ�          $this->errorStr = 'extends�Υѥ������ϥե������ΤߤǤ�';
621          return FALSE;
622        }
623        $this->extends = $param[1];
624        $spos = $lpos + 2;
625        break;
626      case 'include':
627        if (count($in) != 2) {
628          $this->errorStr = 'include�Υѥ�������ꤷ�Ƥ������';
629          return FALSE;
630        }
631        $param = explode('"', $in[1]);
632        if (count($param) != 3) {
633          // Django��ѿ�K����ɤ�          $this->errorStr = 'include�Υѥ������ϥե������ΤߤǤ�';
634          return FALSE;
635        }
636        $node = new GTIncludeNode($param[1]);
637        $spos = $lpos + 2;
638        break;
639      case 'block': // endblock
640        // TODO: check params
641        // TODO: filter block name
642        $blockname = $in[1];
643        if (array_key_exists($blockname, $this->block_dict)) {
644          // TODO: filtered block name print
645          $this->errorStr = 'Ʊ��̾���lock�ϣ��Ĥ������������ޤ���;
646          return FALSE;
647        }
648        $lpos += 2;
649        if ((list($bepos, $blpos) = $this->find_endtags($lpos, $epos, array('endblock'))) === FALSE) {
650          return FALSE;
651        }
652        $nodelist = $this->_parse($lpos, $bepos);
653        $node = new GTBlockNode($blockname, $nodelist);
654        $this->block_dict[$blockname] = &$node; // reference
655        $spos = $blpos;
656        break;
657      case 'for': // endfor
658        // $in[1] = $loopvar, $in[2] = 'in', $in[3] = $sequence, $in[4] = 'reversed'
659        if ((count($in) != 4 && count($in) != 5) || $in[2] != 'in') {
660          $this->errorStr = 'for�Υѥ������ο���Ͻ񼰤����Ǥ�';
661          return FALSE;
662        }
663        if (count($in) == 5) {
664          if ($in[4] == 'reversed') {
665            $reversed = True;
666          } else {
667            $this->errorStr = 'for���Ĥ��ѥ�������eversed�Τ߻�����ޤ���';
668            return FALSE;
669          }
670        } else {
671          $reversed = False;
672        }
673        $lpos += 2;
674        if ((list($bepos, $blpos) = $this->find_endtags($lpos, $epos, array('endfor'))) === FALSE) {
675          return FALSE;
676        }
677        $nodelist = $this->_parse($lpos, $bepos);
678        $node = new GTForNode($in[1], $in[3], $reversed, $nodelist);
679        $spos = $blpos;
680        break;
681      case 'cycle':
682        // TODO: implement namedCycleNodes
683        if (count($in) != 2) {
684          $this->errorStr = 'cycle�ˤϥѥ��������������Ǥ���';
685          return FALSE;
686        }
687        $cyclevars = explode(',', $in[1]);
688        if (count($cyclevars) == 0) {
689          $this->errorStr = 'cycle�ˤ��Ƕ�����ʸ��ɬ��Ǥ���';
690          return FALSE;
691        }
692        $node = new GTCycleNode($cyclevars);
693        $spos = $lpos + 2;
694        break;
695      case 'if': // else, endif
696        array_shift($in);
697        if (count($in) < 1) {
698          $this->errorStr = 'if�Υѥ���������������;
699          return FALSE;
700        }
701        $bitstr = implode(' ', $in);
702        $boolpairs = explode(' and ', $bitstr);
703        $boolvars = array();
704        if (count($boolpairs) == 1) {
705          $link_type = GTIfNode::LINKTYPE_OR;
706          $boolpairs = explode(' or ', $bitstr);
707        } else {
708          $link_type = GTIfNode::LINKTYPE_AND;
709          if (in_array(' or ', $bitstr)) {
710            $this->errorStr = 'if��nd��r�򺮤��뤳�Ȥ��Ǥ��ޤ���;
711            return FALSE;
712          }
713        }
714        foreach ($boolpairs as $boolpair) {
715          // not�ν�
716          if (strpos($boolpair, ' ') !== FALSE) {
717            // TODO: error handling
718            list($not, $boolvar) = explode(' ', $boolpair);
719            if ($not != 'not') {
720              $this->errorStr = 'if��ot��������Ȥ�����Τ�����äƤ��ޤ���';
721              return FALSE;
722            }
723            array_push($boolvars, array(True, $boolvar));
724          } else {
725            array_push($boolvars, array(False, $boolpair));
726          }
727        }
728        $lpos += 2;
729        if ((list($bepos, $eblpos, $nexttag) =
730               $this->find_endtags($lpos, $epos, array('else', 'endif'))) === FALSE) {
731          return FALSE;
732        }
733        $nodelist_true = $this->_parse($lpos, $bepos);
734        if ($nexttag == 'else') {
735          if ((list($bepos, $blpos, $nexttag) =
736                    $this->find_endtags($eblpos, $epos, array('endif'))) === FALSE) {
737            return FALSE;
738          }
739          $nodelist_false = $this->_parse($eblpos, $bepos);
740          $spos = $blpos;
741        } else {
742          $nodelist_false = new GTNodeList();
743          $spos = $eblpos;
744        }
745        $node = new GTIfNode($boolvars, $nodelist_true, $nodelist_false, $link_type);
746        break;
747      case 'debug':
748        $node = new GTDebugNode();
749        $spos = $lpos + 2;
750        break;
751      case 'now':
752        if (count($in) != 2) {
753          $this->errorStr = 'now�Ͻ����ɬ��Ǥ�';
754          return FALSE;
755        }
756        $param = explode('"', $in[1]);
757        if (count($param) != 3) {
758          $this->errorStr = 'now�ν����"�Ǥ����äƤ������';
759          return FALSE;
760        }
761        $node = new GTNowNode($param[1]);
762        $spos = $lpos + 2;
763        break;
764      case 'filter': // endfilter
765        if (count($in) != 2) {
766          $this->errorStr = 'filter�Υѥ���������������;
767          return FALSE;
768        }
769        $lpos += 2;
770        if ((list($bepos, $blpos) = $this->find_endtags($lpos, $epos, array('endfilter'))) === FALSE) {
771          return FALSE;
772        }
773        $filter_expr = new GTFilterExpression('var|'. $in[1]);
774        $nodelist = $this->_parse($lpos, $bepos);
775        $node = new GTFilterNode($filter_expr, $nodelist);
776        $spos = $blpos;
777        break;
778      default:
779        $node = new GTUnknownNode();
780        $spos = $lpos + 2;
781        break;
782    }
783    return $node;
784  }
785
786  // {{ }}��������
787  private function parse_variable(&$spos, &$epos) {
788    $spos += 2;
789    if (($lpos = $this->find_closetag($spos, $epos, self::VARIABLE_TAG_END)) === FALSE) {
790      return FALSE;
791    }
792    // TODO: handle empty {{ }}
793    $fil = new GTFilterExpression(substr($this->template, $spos, $lpos - $spos));
794    $node = new GTVariableNode($fil);
795    $spos = $lpos + 2;
796    return $node;
797  }
798
799  // {# #}��������
800  private function parse_comment(&$spos, &$epos) {
801    $spos += 2;
802    if (($lpos = $this->find_closetag($spos, $epos, self::COMMENT_TAG_END)) === FALSE) {
803      return FALSE;
804    }
805    $spos = $lpos + 2; // #}�Τ���  }
806
807  public function parse_from_file($templatePath) {
808    if (($t = file_get_contents($templatePath)) === FALSE) {
809      $this->errorStr = '�ե����뤬�����ޤ���;
810      return FALSE;
811    }
812    $this->template = $t;
813    $nl = $this->_parse(0, strlen($t));
814    unset($this->template);
815    return new GTFile($nl, $this->block_dict, $this->extends);
816  }
817
818  public function parse($templateStr) {
819    $this->template = $templateStr;
820    $nl = $this->_parse(0, strlen($templateStr));
821    unset($this->template);
822    return $nl;
823  }
824
825  private function _parse($spos, $epos) {
826    $nl = new GTNodeList();
827    while ($spos < $epos) {
828      // �����γ����򸫤Ĥ���     $nspos = strpos($this->template, self::SINGLE_BRACE_START, $spos);
829      if ($nspos === FALSE) {
830        $nspos = $epos;
831      }
832      if ($spos < $nspos) {
833        // �����ʳ�����extNode�Ȥ�����
834        $nl->push(new GTTextNode(substr($this->template, $spos, $nspos - $spos)));
835      }
836      if ($nspos < $epos) {
837        switch (substr($this->template, $nspos, 2)) {
838          case self::BLOCK_TAG_START:
839            if (($node = $this->parse_block($nspos, $epos)) === FALSE) {
840              unset($this->template);
841              return FALSE;
842            }
843            $nl->push($node);
844            break;
845          case self::VARIABLE_TAG_START:
846            if (($node = $this->parse_variable($nspos, $epos)) === FALSE) {
847              unset($this->template);
848              return FALSE;
849            }
850            $nl->push($node);
851            break;
852          case self::COMMENT_TAG_START:
853            if (($node = $this->parse_comment($nspos, $epos)) === FALSE) {
854              unset($this->template);
855              return FALSE;
856            }
857            $nl->push($node);
858            break;
859          default:
860            // ñ�ʤ��ɤߤȤФ�
861            $nspos += 1;
862        }
863      }
864      $spos = $nspos;
865    }
866    return $nl;
867  }
868}
869?>
Note: See TracBrowser for help on using the browser.