Monthly Archives: 3月 2011

MayaaでHTML5のスマートフォンページを作る際にはまったこと

僕はMayaaが好きです。仕事でもかなり使っています。

今回、スマートフォン(iPhoneおよびAndroid対応)向けECサイトのフロントエンドに、Mayaaを使用しました。そこではまったことを報告します。

metaタグにContentTypeが省略できるようになったため、ドキュメント判別の手段が減ってしまった

HTML5では、

<meta charset="UTF-8">

のような書き方が許されるようになりました。

従来は

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />

という書き方をしていました。
これで何が問題なのかというと、Mayaa自体がこのContent-Type指定を読んでいてそれによって処理を判別(例えばapplication/xhtml+xmlかどうかなど)もしているので、動作が異なってしまう場合があります。

私たちの場合は、内部的な事情で、テンプレートファイルの拡張子に.xhtmlを使用していたため、すべてのContentTypeヘッダがapplication/xhtml+xmlになってしまい、ブラウザのバリデーションエラーで画面が真っ白になるという現象が発生しました。

回避方法は、前者の省略記法を使用せず、後者の従来通りの書き方をすることです。この書き方はHTML5文法としても正式です。

aタグがブロック要素を包含できるようになったのに、うまくいかない

この問題は意外と深かったです。テンプレートに

<a href="javascript:alert('hello');">
       <p>aaa</p>
       <p>aaa</p>
</a>

のように記述すると、実行時に勝手に以下のように書き換えられてしまいます。

<a href="javascript:alert('hello');">
       </a><p><a href="javascript:alert('hello');">aaa</a></p>
<p>aaa</p>

これは、Mayaa依存ライブラリのNekoHTMLが、パースした段階でDOMツリーを再構成してしまうためで、NekoHTMLがHTML5に対応していないため、XHTMLやHTML4以前の仕様に基づいて、DOMツリーを構成してしまいます。

この件について、現時点で一番良い回避法は、
http://ml.seasar.org/archives/mayaa-user/2011-March/000923.html
こちらで対応してくださった方法を使用することです。しかし、この場合は、imgやbrなども含めて必ず閉じタグを書かなければなりません。テンプレートコーディング者が第三者の場合などその徹底が難しい場合は使用が難しいと思います。

NekoHTMLについては、本家MLに問い合わせたところ、最新版でもHTML5への特別な対応はしていないとの返答をいただきました。

最終手段として、私は、NekoHTMLのソースを直接書き換えました。修正箇所は、Mayaaが使用しているNekoHTML-0.9.5をベースとして、

org.cyberneko.html.HTMLElements の、

184:     new Element(A, "A", Element.INLINE, BODY, null),

184:     new Element(A, "A", 0, BODY, null),

に書き換えることです。

NekoHTMLにはテストコードが付属しているので、実行してみたところ、2箇所でエラーになりました。

test:
[tester] Parsing test files and generating output...
[tester] Comparing parsed output against canonical output...
[tester] test36.html:5 strings don't match
[tester] [in: )A]
[tester] [out: (P]
[tester] test50.html:5 strings don't match
[tester] [in: )A]
[tester] [out: (P]
[tester] Finished with errors.

BUILD FAILED

これは、まさに今回の修正の対象だったので、テストケースを修正します。
/data/html/canonical/test36.html
修正前:

(HTML
(BODY
(A
Aname foo
)A
(P
(A
Aname foo
"Blah
)A
)P
)BODY
)HTML

修正後:

(HTML
(BODY
(A
Aname foo
(P
"Blah
)P
)A
)BODY
)HTML

/data/html/canonical/test50.html
修正前

(HTML
(BODY
(A
Ahref foo
)A
(P
(A
Ahref foo
"Blah
)A
)P
)BODY
)HTML

修正後

(HTML
(BODY
(A
Ahref foo
(P
"Blah
)P
)A
)BODY
)HTML

test:
[tester] Parsing test files and generating output...
[tester] Comparing parsed output against canonical output...
[tester] Done.

BUILD SUCCESSFUL

これで、独自にビルドしたnekohtmlのjarをアプリのlibにコピーし、nekohtml-0.9.5.jarを削除すれば差し替え完了です。

Mayaaファイルを命名規則から一括作成するEmEditorマクロ

業務でMayaa使っています。
私が所属する開発チームでは、Mayaaのプロセッサとひもづけるid(m:id)に、一定の命名規則を持たせています。

ルールは以下の4種類しかありません。

  1. その場に値を出力する(m:writeプロセッサ):"〜_HERE"
  2. そのタグの属性を変化させる(m:echo, m:attribute):"〜_TAG"
  3. その要素を条件によって出し分ける(m:if):"IF_〜"
  4. その要素を繰り返す(m:for or m:forEach):"LOOP_〜"

このようにしておけば、仮に〜_HEREがm:writeではなく、m:insertであったとしても、テンプレートを書くデザイナーさんには同じように見えるので、よって上記4ルールで徹底運用しています。

さて、日々の業務を楽にするための努力は欠かしません。名前が規則に従っているなら、プログラムも自動生成できてしまうのではないか?

Mayaaは、テンプレートに定義されていないm:idを記述すると、警告で教えてくれる機能があります。

[WARN] EqualsIDInjectionResolver - the injection ID(IF_*******) is not found on the template, /xxx/yyy/zzz.xhtml#410.

この文字列をコピペして食べさせれば、.mayaaファイルの雛形を生成するEmEditorマクロを作りました。

document.selection.Replace(
"[WARN] EqualsIDInjectionResolver - the injection ID(",
"",
eeFindNext | eeFindReplaceCase | eeFindReplaceEscSeq | eeReplaceAll);
document.selection.Replace(
"\\) is not found on the template.*$",
"",
eeFindNext | eeFindReplaceCase | eeFindReplaceEscSeq | eeReplaceAll | eeFindReplaceRegExp);
document.selection.Replace(
"^.*_HERE$",
"\t<!-- を出力します。 -->\n"
+ "\t<m:write m:id='\\0' value='${}' />",
eeFindNext | eeFindReplaceCase | eeFindReplaceEscSeq | eeReplaceAll | eeFindReplaceRegExp);
document.selection.Replace(
"^.*_TAG$",
"\t<!-- します。属性が変化します。 -->\n"
+ "\t<m:echo m:id='\\0'>\n"
+ "\t\t<m:attribute name='' value='${}' />\n"
+ "\t</m:echo>",
eeFindNext | eeFindReplaceCase | eeFindReplaceEscSeq | eeReplaceAll | eeFindReplaceRegExp);
document.selection.Replace(
"^IF.*$",
"\t<!-- の時にのみ表示します。 -->\n"
+ "\t<m:if m:id='\\0' test='${}'>\n"
+ "\t\t<m:echo><m:doBody /></m:echo>\n"
+ "\t</m:if>",
eeFindNext | eeFindReplaceCase | eeFindReplaceEscSeq | eeReplaceAll | eeFindReplaceRegExp);
document.selection.Replace(
"^LOOP.*$",
"\t<!-- を繰り返します。 -->\n"
+ "\t<m:for m:id='\\0'\n"
+ "\t\t\tinit='${}'\n"
+ "\t\t\ttest='${}'\n"
+ "\t\t\tafter='${}'>\n"
+ "\t\t<m:echo><m:doBody /></m:echo>\n"
+ "\t</m:for>",
eeFindNext | eeFindReplaceCase | eeFindReplaceEscSeq | eeReplaceAll | eeFindReplaceRegExp);

便利です!
自分だけですが。。。

問題なのは会社でEmEditorを愛用しているのが僕だけということです。。。秀丸マクロ?書けません!