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

Revision 2554, 28.3 kB (checked in by tasuku, 5 years ago)

r282@dhcp158 (orig r25): tasuku | 2007-09-07 15:42:34 +0900
license toka ireta


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