@IF文の処理を再構成しました

プログラム(機能)関連の開発の話題
アバター
yama
管理人
記事: 2929
登録日時: 2009年7月29日(水) 02:50

@IF文の処理を再構成しました

投稿記事by yama » 2017年8月15日(火) 09:26

https://github.com/modxcms-jp/evolution ... 54bea3.zip
@IF文の処理を最初から作り直しました。どなたかテストいただけると助かります。

既存の処理では判定のたびにネストの深さなどの情報をトレースしていましたが、今回はテキストデータ全体をPHP文に変換し、PHP側に判定を丸投げする形にしました。つまりCMS側が自力でパースを行なうのではなく、PHPにパースを任せる形です。既存のテンプレートエンジンではSmartyなどがこの方式を採用しています。

コード: 全て選択

<@IF:[*id:is(1)*]>
これはトップページです
<@ELSE>
トップページではありません
<@ENDIF>

たとえば上記のようなテキストであれば

コード: 全て選択

<?php if(ifTrue('[*id:is(1)*]')):?>
これはトップページです
<?php else: ?>
トップページではありません
<?php endif;?>

このように内部的に変換した上でPHP文として評価し、その結果を受け取ります。
アバター
hisato
メンバー
メンバー
記事: 71
登録日時: 2012年8月18日(土) 20:21

@IF文の処理を再構成しました

投稿記事by hisato » 2017年8月15日(火) 16:57

Evoに移行前のサイトを保管してあったのでそこで試してみましたが、自サイトの範囲内では問題なく動作しているようでした。(多くて3つぐらいのネストしかしていません)

あと個人的要望なのですが @IFやモディファイアなどでMODX変数(プレースホルダや一時スニペットなどもできれば…)を評価する時に定義されていなければ false になると嬉しいです。
おそらく今は変数名の文字列の状態でtrueになる→ [[test]]ならstring8文字

もしくは定義されているか判断するモディファイアは可能でしょうか?
少し自分で考えてみたのですがどうやっていいか分からず…。
アバター
yama
管理人
記事: 2929
登録日時: 2009年7月29日(水) 02:50

@IF文の処理を再構成しました

投稿記事by yama » 2017年8月22日(火) 05:45

確認ありがとうございます。たぶん、かなり深いネストでも大丈夫だと思います。
定義されていなければ false に
了解です、後ほど対応します
ryocka
メンバー
メンバー
記事: 51
登録日時: 2014年4月28日(月) 00:06

@IF文の処理を再構成しました

投稿記事by ryocka » 2017年8月26日(土) 00:00

こんばんは、動作を確認しようと思うのですが document.parser.class.inc.php の上書きのみで大丈夫でしょうか?
アバター
yama
管理人
記事: 2929
登録日時: 2009年7月29日(水) 02:50

@IF文の処理を再構成しました

投稿記事by yama » 2017年10月19日(木) 08:01

https://github.com/modxcms-jp/evolution ... evelop.zip
@IF構文の処理をさらに改善しました。前回は「どこからどこまでが@IF、どこからどこまでが@ELSE」といった切り出し処理を正確・高速に行なうように改善しましたが、今回は@IF・@ELSEIF内の判定処理を改善しました。

本来はフィルターとして利用すべきモディファイアの機能を流用して複雑な分岐処理を実装するケースが増えて、テンプレートワークが分かりにくくなりがちだったのを解決するために@IF構文の開発を進めていました。テンプレートエンジンの実装を行わないことがMODXの思想のひとつでしたが、実際には需要があり、裏技のような実装が普及していました。@IF構文では、モディファイア単体で実装できないレベルの高度な分岐処理を実装する目標がありましたが、今回の改善で実現できたと思います。

コード: 全て選択

<@IF:[*longtitle*]><h1>[*longtitle*]</h1><@ENDIF>

これまでは、たとえば上記のように書いた場合、[*longtitle*]の値が空だと判定処理ではエラーが発生していました。

コード: 全て選択

<@IF:[*longtitle:isNotEmpty*]><h1>[*longtitle*]</h1><@ENDIF>

そのため、上記のように:isNotEmptyモディファイアなどを用いて、「空でなければ1を、空であれば0を返す」という判定をさらに加えて対応する必要がありました。そのように書くほうが意味が分かりやすくてよいかもしれませんが、できるだけ少ない文字数に抑えたいこともあると思います。

コード: 全て選択

<@IF:[*longtitle:isNotEmpty:then(1):else(0)*]><h1>[*longtitle*]</h1><@ENDIF>

もともとは上記のように書く必要がありましたが、

コード: 全て選択

<@IF:[*longtitle*]><h1>[*longtitle*]</h1><@ENDIF>

今回の改善では、上記のように書くだけで動作します。モディファイア処理にスイッチしないため、実際は誤差の範囲ですが、理論的には動作も少しだけ軽くなります。

コード: 全て選択

<@IF:[*description*]><meta name="description" content="[*description*]"><@ENDIF>

[*description*]に何も入力していない場合は何も出力しません。

また、たとえば「郵便番号」というテンプレート変数を設置したとして、その値が「123-4567」だった場合、

コード: 全て選択

<@IF:[*郵便番号*]>[*郵便番号*]<@ENDIF>

「123-4567」を数式と見なして計算してしまい「-4687」として結果的にはfalse判定になることがある問題も修正しました。ただし書き方を少し変更する必要があります。

コード: 全て選択

<@IF:"[*郵便番号*]">[*郵便番号*]<@ENDIF>

コード: 全て選択

<@IF:'[*郵便番号*]'>[*郵便番号*]<@ENDIF>

上記のように、ダブルクォートまたはシングルクォートで囲みます。囲まない場合は、数式として判定できる文字列が含まれている場合は数式として判定します。この仕様はPHPプログラミングに倣っています(もう少しスタイリッシュな方法はないかという気もしますが)。工夫して使い分ければ、たとえばテンプレート変数の値を日付文字列と見なして、何日前になったら色を変更するなどといった処理をモディファイアなどを利用して簡単に作ることができると思います。
クォートで囲まなければ従来どおりの動作です。実際には、数式として自動的に判定されたくないケースのほうが多いはずなので、明示的にクォートで囲むことを習慣付けるのがよいかもしれません。

コード: 全て選択

<@IF:20-[[今日の日付]]==1>
今日は一日前です
<@ENDIF>
あるいは、ビルトインの機能だけで済ませるなら、

コード: 全て選択

<@IF:20-[[$_SERVER['REQUEST_TIME']:date(d)]]==1>
今日は一日前です
<@ENDIF>

式では数字以外に「 - + < = / * ( ) % 」などの文字を使えますので、上記のような判定ができます。これ以外の文字を含む場合は数式文字列とは判定せず、無条件に「1」という値に変換します。

「 [*longtitle*]の文字数が10文字を超える場合は[*pagetitle*]を表示する」 といった実装を行なう場合、

コード: 全て選択

<@IF:[*longtitle:strlen*]>10><h1>[*pagetitle*]</h1><@ENDIF>

上記は動作しません。

コード: 全て選択

<@IF:10<[*longtitle:strlen*]><h1>[*pagetitle*]</h1><@ENDIF>

上記のように書く必要があります。現時点では式の中で「>」を使うことができないためです。これを無理なく実装するためには@IF構文の書式を少し変更する必要があり、互換性の解決方法も含めて今後検討します。

また、冒頭に「!」を付けることで判定を反転させる仕様がありますが、これはIF判定内で使えるケースが限られるという問題がありました。

コード: 全て選択

<@IF:![*longtitle*]&&![*alias*]>[*pagetitle*]<@ENDIF>

たとえば上記のように、&&や||で複数の条件を重ねたケースで使えませんでした。今回の改善で、ほぼ自由に使えるようになりました。

@IFやモディファイアなどでMODX変数(プレースホルダや一時スニペットなどもできれば…)を評価する時に定義されていなければ false に

viewtopic.php?p=9541#p9541
上記の件、対応しました。
アバター
yama
管理人
記事: 2929
登録日時: 2009年7月29日(水) 02:50

@IF文の処理を再構成しました

投稿記事by yama » 2017年10月20日(金) 13:34

数式判定を回避するためにクォートで囲むのは、正式リリース時には採用しないかもしれません。もう少しスマートな方法がないか検討します。
アバター
yama
管理人
記事: 2929
登録日時: 2009年7月29日(水) 02:50

@IF文の処理を再構成しました

投稿記事by yama » 2017年10月24日(火) 09:17

計算が必要な場合はモディファイアを使う仕様を検討しています。クォートの使い分けで手軽に計算式を用いることができる仕様を検討していましたが、電話番号を数式と見なさずに単純に文字列として表示する使い方をしたい場合にクォートで囲み忘れると、値によって表示されたりされなかったりということになり、問題が発生していても気付きにくいケースが多発する可能性があります。上級者向けの利便よりも、初心者が陥りやすく、且つ気付きにくいうっかりミスを防ぐことが重要と考えます。

コード: 全て選択

<@IF:[*電話番号*]><h1>[*電話番号*]件</h1><@ENDIF>

[*電話番号*]が「01-2345-6789」だとしても、数式と見なさずそのまま表示します。
自動的に数式として扱ってしまうと、「9999-1111」なら表示されるのに「01-9999-1111」は表示されず、「市外局番をつけるとなぜか表示されない。文字数が多いと誤動作する?」というような紛らわしい状況になってしまうので、それは今回の仕様では回避します。

コード: 全て選択

<@IF:[*値*]>[*値*]件<@ENDIF>

[*値*]がゼロの場合は上記は表示しません。値が入力されていない場合も表示しません。

コード: 全て選択

<@IF:[*値:strlen:gt(0)*]>[*値*]件<@ENDIF>

上記のように書くと、値がゼロの場合は表示します。

コード: 全て選択

<@IF:[*値:strlen:is(0)*]>処理待ち<@ELSEIF:[*値:is(0)*]>処理なし<@ELSE>[*値*]件<@ENDIF>

値がない場合・ゼロの場合・ゼロ以外が入力されている場合の3ケースに応じて出し分けます。

コード: 全て選択

<@IF:[[今日の日付:calc(20-%s)]]==1>
今日は一日前です
<@ENDIF>
あるいは、ビルトインの機能だけで済ませるなら、

コード: 全て選択

<@IF:[[$_SERVER['REQUEST_TIME']:date(d):calc(20-%s):is(1)]]>
今日は一日前です
<@ENDIF>

計算が必要な場合は、上記のようにモディファイアを使います。「%s」に値が代入されます。「%s」以外に、同じ意味を持つプレースホルダとして「?」「 [+value+] 」などを好みに応じて使えます。前回提案した仕様と比べると複雑に見えますが、単純な使い方をしたい場合にうっかりミスが発生する可能性を減らせます。

後日、開発版に関して上記のように変更します。
masco
メンバー
メンバー
記事: 107
登録日時: 2014年9月26日(金) 10:43

@IF文の処理を再構成しました

投稿記事by masco » 2017年11月20日(月) 16:31

PHP:7.1.9
MySQL:5.5.5-10.1.26-MariaDB
xamppでローカル環境で作成

こんにちは。
1.0.19Jで更新日が最新のものを使用しています。
スニペットを作成して$modx->setPlaceholder('placeholder', 1);で[+placeholder+]に値を入れました。

しかし下記を記述しても条件分岐が反映されず「0」になります。
<@IF:[+placeholder+]>
1
<@ELSE>
0
<@ENDIF>

最新のもので作成したのでどの時点からかは不明ですが、関係がありそうなのでこちらに書きました。
よろしくお願いいたします。