Nucleus

スキン変数の入れ子構造

2007年5月15日

Nucleus のスキン変数・テンプレート変数は、入れ子で使えない。また、タグの始まりは『<%』で終わりは『%>』というルールになっているが、どちらの表記をどちらに用いても、エラーにならない。つまり、

<%blog(default/index,10)%>
%>blog(default/index,10)%>
<%blog(default/index,10)<%
%>blog(default/index,10)<%

はどれも、エラーなく同じように機能する。

なので、入れ子構造のスキンを例えば次のように表記すると

<%blog(default/index,<%blogsetting(id)%>)%>

これは思ったように機能しない。このコードは、次のコードと等価であるからだ。

<%blog(default/index,%>blogsetting(id)<%)%>

『blogsetting(id)』はタグの外なのでそのまま表示され、『<%)%>』というタグは存在しなため、『<%)()%>』が表示される。

これは、PARSER::parse() 関数の仕様によるものである。この関数を次のように書き換えれば、入れ子のタグを表示できる。

    function parse($contents) {
        $delim=preg_replace('/^\((.*)\)$/','$1',$this->delim);
        list($begin,$end)=explode('|',$delim);
        if (!$begin || !$end) list($begin,$end)=array('<%','%>');
        $stack=array();
        while (strlen($contents)) {
            if (count($stack)==0) {
                // Outside the tag
                $i=strpos($contents,$begin /*'<%'*/);
                if ($i===false) {
                    echo $contents;
                    return;
                }
                echo substr($contents,0,$i);                            // Left side
                $contents=substr($contents,$i+strlen($begin /*'<%'*/)); // Right side
                array_push($stack,'');
            }
            // Now, we are in the tag.
            $i=strpos($contents,$begin /*'<%'*/);
            $j=strpos($contents,$end /*'%>'*/);
            if ($i===false && $j===false) {
                // Both '<%' and '%>' are not found.
                $this->doAction($contents);
                return;
            } else if ($j!==false && ($j<$i || $i===false)) {
                // '%>' found. The right characters are outside the tag.
                $action=substr($contents,0,$j);                       // Left side
                $contents=substr($contents,$j+strlen($end /*'%>'*/)); // Right side
                $actionlc=preg_replace('/^([^\(]+)[\(](.*)$/','$1',strtolower($action));
                if (preg_match('/^(if[.]*|else|endif|ifnot|elseif|elseifnot)$/',$actionlc)) {
                    // if, else, endif etc.
                    // ob_start() cannot be used here because it's used below.
                    // In addition, on_start() isn't needed here (if etc. doesn't parse anything).
                    $this->doAction($action);
                } else {
                    ob_start();
                    $this->doAction($action);
                    $contents=ob_get_contents().$contents;
                    ob_end_clean();
                }
                if (count($stack)) $contents=array_pop($stack).$contents;
            } else {
                // '<%' found.
                array_push($stack,substr($contents,0,$i));              // Left side
                $contents=substr($contents,$i+strlen($begin /*'<%'*/)); // Right side
            }
        }
    }

これを、元の PARSER::parse() 関数と入れ替えればよい。ただ、オリジナルのparse()関数は非常に効率の良いコードで、入れ子構造が必要のないときは、スピードの上で上記のコードはオリジナルのコードにはるか及ばない。なので、コアを変更するよりもプラグインで上記のコードを利用できるようにする、つまり必要なときだけ上記のコードを利用することを考えてみたい。
 スキンを入れ子で使えるプラグインはyuさんのNP_IncludeExがあるが、ネストのレベルは一段だけ対応しているようだ。上記のコードは、多段回のネストに対応しているはず。

(スキン変数をネストして用いると、予期せぬセキュリティーホールが発生する可能性もありますので、使用の際は十分注意してください。)

コメント

コメントはありません

コメント送信