Jinja の Whitespace Control

JinjaWhitespace Controlは使いやすくて便利なのだけれども。

あたしはどっちかつぅとアホなので、macro やら割と入り組んだ if やら for やらで包んでるうちに、だんだん制御しにくくなってきて、Whitespace Control ごときで試行錯誤ちっくになってきて、フラストレーションになってくることが多くて。

なんか憶えやすくてシンプルなルールで制御出来んかなぁ、と。

ツリーをなしている python 辞書「node」から XML を生成したくて、こんなのを書いていた:

 1 {# === #}
 2 {%- macro render_attrs(nd, idx=0) %}
 3 {%- for ch in nd["childs"] %}
 4 {%- if ch["xmlnode_type"] == "attr" %} {{ch|origname}}="{{ch|testval(idx)}}"{% endif -%}
 5 {%- endfor %}
 6 {%- endmacro -%}
 7 {# === #}
 8 {%- macro render_node(nd, idx_outer) %}
 9 {%- for idx in range(5) %}
10 {%- if nd["xmlnode_type"] != "attr" %}
11 {{"  " * nd.hier}}<{{nd|origname}}{{render_attrs(nd, idx + idx_outer)}}>
12 {%- if "childs" not in nd %}
13 {{"  " * nd.hier}}{{nd|testval(idx + idx_outer)}}
14 {%- endif %}
15 {%- endif %}
16 {%- if "childs" not in nd -%}
17 {%- if nd["xmlnode_type"] != "attr" %}
18 {{"  " * nd.hier}}</{{nd|origname}}>
19 {%- endif -%}
20 {%- endif -%}
21 {%- for ch in nd["childs"] %}
22 {{- render_node(ch, idx + idx_outer) }}
23 {%- endfor %}
24 {%- if "childs" in nd %}
25 {%- if nd["xmlnode_type"] != "attr" %}
26 {{"  " * nd.hier}}</{{nd|origname}}>
27 {%- endif %}
28 {%- endif %}
29 {%- endfor %}
30 {%- endmacro -%}
31 <?xml version="1.0" encoding="UTF-8"?>
32 <doc>
33 {{- render_node(structs["coldefs"], 0) }}
34 </doc>

わけわからんよね。中身は気にしないでくださいな。カスタムフィルタとかいるんで、みなはまがそのまんま真似出来るような「サンプル」としては機能しませぬし。着目するのは「マイナス記号」だけです。マイナス記号がついてる側の余分な white space を手動で取り除ける、というわけです。(自動でやる方法は本家ドキュメントに書かれてるんで自分で調べてね。)

で、これで処理すると例えばこんななわけだ:

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <doc>
 3   <SunriseSunset>
 4     <Keys airport="S003E" day="S004E">
 5     </Keys>
 6     <SunriseAstronomicalTwilightStarttime>
 7     S005E
 8     </SunriseAstronomicalTwilightStarttime>
 9     <SunriseNauticalTwilightStarttime>
10     S006E
11     </SunriseNauticalTwilightStarttime>
12     <SunriseCivilTwilightStarttime>
13     S007E
14     </SunriseCivilTwilightStarttime>
15     <SunriseTime>
16     S008E
17     </SunriseTime>
18     <SolarNoonTime>
19     S009E
20     </SolarNoonTime>
21     <SunsetTime>
22     S010E
23     </SunsetTime>
24     <SunsetCivilTwilightEndtime>
25     S011E
26     </SunsetCivilTwilightEndtime>
27     <SunsetNauticalTwilightEndtime>
28     S012E
29     </SunsetNauticalTwilightEndtime>
30     <SunsetAstronomicalTwilightEndtime>
31     S013E
32     </SunsetAstronomicalTwilightEndtime>
33   </SunriseSunset>
34   <!-- ... -->
35 </doc>

まぁ最初の結果としてはこれでもいいんだけれど、冗長過ぎて一瞥性に欠けるし手書きで保守するのも鬱陶しいようなシロモノだ、こうならいいのに:

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <doc>
 3   <SunriseSunset>
 4     <Keys airport="S003E" day="S004E"/>
 5     <SunriseAstronomicalTwilightStarttime>S005E</SunriseAstronomicalTwilightStarttime>
 6     <SunriseNauticalTwilightStarttime>S006E</SunriseNauticalTwilightStarttime>
 7     <SunriseCivilTwilightStarttime>S007E</SunriseCivilTwilightStarttime>
 8     <SunriseTime>S008E</SunriseTime>
 9     <SolarNoonTime>S009E</SolarNoonTime>
10     <SunsetTime>S010E</SunsetTime>
11     <SunsetCivilTwilightEndtime>S011E</SunsetCivilTwilightEndtime>
12     <SunsetNauticalTwilightEndtime>S012E</SunsetNauticalTwilightEndtime>
13     <SunsetAstronomicalTwilightEndtime>S013E</SunsetAstronomicalTwilightEndtime>
14   </SunriseSunset>
15   <!-- ... -->
16 </doc>

でこうなってくるとくだんの「試行錯誤」が始まっちゃって、無駄に時間を費やす、と。そもそもツリーのノードトラバースだけでも結構脳みそ疲弊するしね、疲れるのよ。

面倒なので結果的にどんなテンプレートにしたのかは書かないけれど、この書き換えの過程で学習した一つのルールは、まぁおよそこんな感じなの。

つまり確実に改行したい場所にだけ神経を注げば良く、こうする:

1 {%- if True %}      {# ブロックの前の white space は取り除く #}
2                     {# 空行を書いておけば強制改行と等価 #}
3 {{ render_hoge() }} {# 本題 #}
4 {%- endif %}        {# ここは無頓着でも良いかも #}

要するに試行錯誤の原因は言ってみればパレート分析みたいなもんで、「あっちを立てればこっちが立たず」と格闘するからなんだけれど、頭痛の種の原因のトップが「取っ払い過ぎて入れたい改行・空白まで失ってしまう」ことなのね。でも「ブロックの外を取り除き、ブロック内で確実に入れたい改行・空白を入れる」だけをルールにしとけば、まぁそんなに悩まないかなと。こんなことにすぐに気付かないアタシはアホですけどな。

あ、あとちなみにですが、macro 内で結構無頓着に書いても、展開時に取り除くことが出来る:

1 {{- render_hoge() -}}

なんてことも意識しとくといいかな、てのは、結構前に学んだ。