カテゴリ毎の件数を出力する/メニュー項目選択肢をデザイン定義に読み込む

CMS Designerを「こんな風に使ってます」等の活用事例や、自分なりのTIPS等のご報告をお待ちしています。
返信
webmaster
Site Admin
記事: 1451
登録日時: 2004年12月10日(金) 10:09

カテゴリ毎の件数を出力する/メニュー項目選択肢をデザイン定義に読み込む

投稿記事 by webmaster » 2006年10月05日(木) 17:27

メニュー項目の選択肢をデザイン定義側に読み込んで再利用したり、
選択肢毎のエントリ件数を表示したりする方法を説明します。

この方法はXSLTのかなり高度な使用方法になっており、職業プログラマでないと
理解は難しいかもしれません。
なるべくデザイナーでもサンプルをコピーして使えるよう、説明したつもりですが、
この内容はTIPSというよりHackのレベルですので、よく分からなくて普通だと思います。

また、CMS Designerにも精通している必要があります。

そもそもデザイナーさんにプログラマの真似事をして頂く事を想定してはおりませんので、
よく分からない場合は申し訳有りませんがこのサンプルの利用を断念して下さい。m(__)m

今後、もっと簡単な方法で実現できる機能をCMSDに追加することを検討致します。

-----------------------------------------------------------------------

スキーマでメニュー(menu)項目を使った場合に面倒なのが、デザイン定義での
<xsl:if>による振り分けです。
例えば

コード: 全て選択

<data name="shopkind" type="menu" caption="お店種別"  >
  <menuitem id="1">中華</menuitem>
  <menuitem id="2">ラーメン</menuitem>
  <menuitem id="3">和食</menuitem>
  <menuitem id="4">洋食</menuitem>
  <menuitem id="5">スイーツ</menuitem>
</data>
というメニュー項目がある場合、

コード: 全て選択

<xsl:value-of select="shopkind" />
上記のようなデザイン定義を使って「洋食」が設定されたエントリを表示しても、

コード: 全て選択

4
のように、「洋食」ではなく「4」、つまりidしか表示されません。
これは、エントリデータ上では選択肢の表示情報ではなく、idのみしか格納
されていない為です。
なぜこのような仕様になっているかというと、「中華」や「洋食」という情報は
見た目の情報、つまりデザインであって、コンテンツではないという考え方からです。

このようにコンテンツとデザインが分離されている為、メニュー項目の選択値に
対して「和食」ではなく「japanese food」のように英語で表示したり、文字ではなく
画像を表示したり、または何かを表示する/しないを切り替えたり、など様々な
処理を行う事が可能になっています。

しかし、このような仕様になっている為、普通に「中華」「ラーメン」のように
表示したい場合、次のようなデザイン定義を行う必要があり、面倒です。

コード: 全て選択

<xsl:if test="shopkind='1'">中華</xsl:if>
<xsl:if test="shopkind='2'">ラーメン</xsl:if>
<xsl:if test="shopkind='3'">和食</xsl:if>
<xsl:if test="shopkind='4'">洋食</xsl:if>
<xsl:if test="shopkind='5'">スイーツ</xsl:if>
このやり方だと、もしスキーマ側に選択肢を追加した場合に、デザイン定義側も
一緒に追加する必要があります。これは非常に面倒ですし、例えば次のような
「都道府県」のメニュー項目だった場合は、デザイン定義自体が長大なものに
なってしまいます。

コード: 全て選択

<data name="pref" type="menu" caption="都道府県" group="True">
	<menuitem id="01">北海道</menuitem>
	<menuitem id="02">青森県</menuitem>
	<menuitem id="03">岩手県</menuitem>
	<menuitem id="04">宮城県</menuitem>
	<menuitem id="05">秋田県</menuitem>
	<menuitem id="06">山形県</menuitem>
	<menuitem id="07">福島県</menuitem>
	<menuitem id="08">茨城県</menuitem>
	<menuitem id="09">栃木県</menuitem>
	<menuitem id="10">群馬県</menuitem>
	<menuitem id="11">埼玉県</menuitem>
	<menuitem id="12">千葉県</menuitem>
	<menuitem id="13">東京都</menuitem>
	<menuitem id="14">神奈川県</menuitem>
	<menuitem id="15">新潟県</menuitem>
	<menuitem id="16">富山県</menuitem>
	<menuitem id="17">石川県</menuitem>
	<menuitem id="18">福井県</menuitem>
	<menuitem id="19">山梨県</menuitem>
	<menuitem id="20">長野県</menuitem>
	<menuitem id="21">岐阜県</menuitem>
	<menuitem id="22">静岡県</menuitem>
	<menuitem id="23">愛知県</menuitem>
	<menuitem id="24">三重県</menuitem>
	<menuitem id="25">滋賀県</menuitem>
	<menuitem id="26">京都府</menuitem>
	<menuitem id="27">大阪府</menuitem>
	<menuitem id="28">兵庫県</menuitem>
	<menuitem id="29">奈良県</menuitem>
	<menuitem id="30">和歌山県</menuitem>
	<menuitem id="31">鳥取県</menuitem>
	<menuitem id="32">島根県</menuitem>
	<menuitem id="33">岡山県</menuitem>
	<menuitem id="34">広島県</menuitem>
	<menuitem id="35">山口県</menuitem>
	<menuitem id="36">徳島県</menuitem>
	<menuitem id="37">香川県</menuitem>
	<menuitem id="38">愛媛県</menuitem>
	<menuitem id="39">高知県</menuitem>
	<menuitem id="40">福岡県</menuitem>
	<menuitem id="41">佐賀県</menuitem>
	<menuitem id="42">長崎県</menuitem>
	<menuitem id="43">熊本県</menuitem>
	<menuitem id="44">大分県</menuitem>
	<menuitem id="45">宮崎県</menuitem>
	<menuitem id="46">鹿児島県</menuitem>
	<menuitem id="47">沖縄県</menuitem>
	<menuitem id="80">海外</menuitem>
</data>
都道府県の選択肢は既にスキーマ側に記述されているのですから、それを
デザイン定義側で再利用できないものでしょうか。

つまり、エントリデータに"01"と入っていたら、スキーマのmenuitemを読み込んで、
対応する"北海道"という文字列に変換して出力できないか、ということです。

実は、多少難解な方法なのですが、XSLTにはそれを実現する方法が用意
されています。
今から順を追ってそれを説明します。

最初に、最終的なデザイン定義の全体を示します。前提とするスキーマは
先ほどの都道府県のスキーマです。
スキーマファイル名を prefsample.schema.xml とします。
デザイン定義のファイル名は prefsample.list.default.xsl とします。

コード: 全て選択

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
	<xsl:output method="xml" encoding="EUC-JP" omit-xml-declaration="yes" />
	
	<!-- スキーマファイルから都道府県リストを読み込み、$preflist という変数に格納する -->
	<xsl:variable name="preflist" select="document('prefsample.schema.xml')/schema/data[@name='pref']/menuitem" />
	
	<!-- エントリ一覧のテンプレート開始 -->
	<xsl:template match="/entrylist">
	
	<!-- エントリ一覧表示ループ -->
	<xsl:for-each select="entry">

		<!-- pref項目の値を変数$prefに格納 -->
		<xsl:variable name="pref" select="pref/text()" />

		<!-- 都道府県リストから、このエントリのprefに一致するtext()を取得して表示する。 -->
		<xsl:value-of select="$preflist[@id=$pref]/text()" />
	
	</xsl:for-each>
	</xsl:template>
</xsl:stylesheet>
1行ずつ説明します。

コード: 全て選択

	<!-- スキーマファイルから都道府県リストを読み込み、$preflist という変数に格納する -->
	<xsl:variable name="preflist" select="document('prefsample.schema.xml')/schema/data[@name='pref']/menuitem" />
この行で、このデザイン定義中に prefsample.schema.xml の都道府県リストを
読み込んでいます。
<xsl:variable>とは、独自のデータを定義するXSLTの命令で、例えば

コード: 全て選択

<xsl:variable name="myname" select="'○○○会社'" />
のように書くと、

コード: 全て選択

<xsl:value-of select="$myname" />
のように「$」を先頭につけて呼び出すことでXSL中のどこからでも'○○○会社'
という文字列を利用することができるようになります。

そしてこの都道府県のサンプルでは、select属性に「document」という命令が
記述されています。
これは、外部のXMLファイルからデータを読み込んでこい、という命令です。

コード: 全て選択

document('prefsample.schema.xml')
で、prefsample.schema.xml 内の全てのXML情報が読み込まれます。次に、

コード: 全て選択

document('prefsample.schema.xml')/schema/data[@name='pref']
のようにdocument命令の後にスラッシュをつけてXML中のノードを選択して
いきます。
この例では、schema要素中のdata要素を選択し、そのdata要素のうちname属性が
'pref'のもの(つまり都道府県menu項目)のみに絞り込んでいます。
(要素名の後ろに[]をつけて条件式を書くと、その条件式に合致する要素のみ
を選択しろ、という命令になります)

今回欲しいのはメニュー項目の選択肢のみなので、スキーマのdata要素内の
menuitem要素だけをさらに選択します。

コード: 全て選択

document('prefsample.schema.xml')/schema/data[@name='pref']/menuitem
これによって、$preflist に、

コード: 全て選択

	<menuitem id="01">北海道</menuitem>
	<menuitem id="02">青森県</menuitem>
	<menuitem id="03">岩手県</menuitem>
	<menuitem id="04">宮城県</menuitem>
		:
	<menuitem id="47">沖縄県</menuitem>
	<menuitem id="80">海外</menuitem>
というデータが格納されます。

難しいと思ったら細部まで理解しなくて結構です。この行でだいたいどういう事を
行っているのかだけ、理解できればOKです。
ともかく、$preflistに対して、prefsample.schema.xmlのメニュー項目prefの選択肢
を読み込んだ、ということです。

この$preflistを利用するにはどのようにすれば良いでしょうか。
$preflistは現在、menuitem要素のリストになっているので、for-each命令を
使ってループさせることで、全ての選択肢を出力させることができます。

コード: 全て選択

<xsl:for-each select="$preflist">
	<xsl:value-of select="text()" /><br />
</xsl:for-each>
この結果は、

コード: 全て選択

北海道
青森県
岩手県
宮城県
 :
沖縄県
海外
となります。以上、最後の全都道府県の出力部分は余談でした。

話を元に戻します。
スキーマに記述された都道府県のリストを$preflistに格納するところまで説明しました。

元々の要望は、エントリデータに格納されたメニュー項目のidに対応する
選択肢を表示したい("01"なら"北海道"と表示したい)、というものです。

エントリデータに格納されたメニュー項目のidとは、ここでは pref の事です。

コード: 全て選択

<xsl:value-of select="pref" />
と書けば、"04"や"12"など、idが出力されます。
今からやりたいのは、$preflistのid属性の中から、prefの値に一致するmenuitem要素を
見つけてその内容(「宮城県」、や「千葉県」など)を取得する、ということです。

エントリ一覧表示ループの中を見ると、

コード: 全て選択

		<!-- pref項目の値を変数$prefに格納 -->
		<xsl:variable name="pref" select="pref/text()" />

		<!-- 都道府県リストから、このエントリのprefに一致するtext()を取得して表示する。 -->
		<xsl:value-of select="$preflist[@id=$pref]/text()" />
となっています。
先に2つめの行を見ると、<xsl:value-of>を使って、$preflist[@id=$pref]/text()
を出力しています。
つまり、$preflistの選択肢の中で、id属性が$prefと一致するものを探し、
その内容を出力しているという意味になります。

上の行で$prefには現在のエントリのpref要素の内容をデータとして設定しています。
既に説明した通り、pref要素にはメニュー項目のidが格納されています。

つまり、$pref(pref)に一致するid属性を持った$preflist(menuitem)
の内容を出力せよ、という命令です。

これで、prefが"04"なら"宮城県"が出力されます。

非常にややこしいと思いますが、何度も繰り返し読んで理解してみて下さい。
よく分からなければ、コピーして使用してくださって結構です。

ちなみに、なぜわざわざprefを$prefにコピーしているかというと、
2つめの行の[]内ではprefが直接参照できないからです。

以上、スキーマのメニュー項目選択肢をデザイン定義で読み込んで使う方法の
ご紹介でした。

-----------------------------------------------------------------------------------

もう一つ、メニュー項目を「カテゴリ」と見なして、そのカテゴリに何件のエントリが
登録されているかを出力する方法をご紹介します。

上の方法の延長線になります。

コード: 全て選択

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xhtml" encoding="EUC-JP" omit-xml-declaration="yes" />

<!-- スキーマファイルから都道府県リストを読み込み、$preflist という変数に格納する -->
<xsl:variable name="preflist" select="document('prefsample.schema.xml')/schema/data[@name='pref']/menuitem" />

<!-- エントリ一覧のテンプレート開始 -->
<xsl:template match="/entrylist">

	<!-- エントリのpref項目のみを$entryprefsに格納 -->
	<xsl:variable name="entryprefs" select="entry/pref" />
	
	<!-- 都道府県のリストを出力 -->
	<xsl:for-each select="$preflist">
		<xsl:variable name="prefid" select="@id" />
		<xsl:value-of select="text()" />
		(<xsl:value-of select="count($entryprefs[text()=$prefid])" />)<br />
	</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
このサンプルの肝は、

コード: 全て選択

		(<xsl:value-of select="count($entryprefs[text()=$prefid])" />)<br />
です。
count()命令は、()内の要素が何件あるかを返すXSLTの命令です。
$entryprefsには全エントリの都道府県項目のみが抽出して格納されているので、

コード: 全て選択

count($entryprefs)
と書けば、エントリ件数が取得できます。しかしここでは$entryprefsを
[]内の条件で絞り込んでいます。その条件とは、「内容が$prefidと一致するもの」
です。

つまり、$prefidが"01"なら、エントリデータが北海道のものだけを抽出せよ、
ということです。そのcountを取っているのが先ほどのサンプルです。

$prefidは、$preflistのfor-eachループの中で"01"→"02"→"03"…と変わって
いくので、全都道府県分の件数が出力されることになります。

出力は、例えば

コード: 全て選択

北海道(2)
青森県(0)
岩手県(5)
宮城県(13)
 :
のようになります。

以上、駆け足でしたがテクニックのご紹介でした。
動作確認済のサンプルからの抜粋の為、ひょっとしたら抜粋ミスをしている箇所が
あるかもしれません。その場合はご連絡頂ければ幸いです。
最後に編集したユーザー webmaster on 2006年10月05日(木) 19:42 [ 編集 2 回目 ]

tsu
パワーユーザー
記事: 208
登録日時: 2006年1月16日(月) 12:00
お住まい: さいたま

投稿記事 by tsu » 2006年10月05日(木) 19:03

色々探りdocument関数を使い変数へ代入まではよかったのですが、その先がまったくはなしになってなかったです、、、。
とても勉強になりました!もっと勉強しないとな、、、。と思います。

URLをつける場合

コード: 全て選択

<!-- 都道府県のリストを出力 -->
<xsl:for-each select="$preflist">
<a href="prefsample.php?pref={$prefid}">
<xsl:variable name="prefid" select="@id" />
</a>
<xsl:value-of select="text()" />
(<xsl:value-of select="count($entryprefs[text()=$prefid])" />)<br />
</xsl:for-each>

webmaster
Site Admin
記事: 1451
登録日時: 2004年12月10日(金) 10:09

投稿記事 by webmaster » 2006年10月05日(木) 19:55

 tsuさん、いつもありがとうございます。

 今回のTIPSはTIPSというよりHackに近く、CMSDに該当する機能がない為の
苦し紛れの回避策と言える内容でございまして、お恥ずかしい限りです。

 XSLTのスキルアップにはちょうど良いかもしれませんので、ご参考になれば幸いです。

 また、都道府県一覧に、各都道府県別の一覧へのリンクを付ける方法を提示して頂き
ありがとうございます。
 これがないと、都道府県別の件数を出したところで意味がなかったですね。:)

 一部記述ミスと思われる箇所がありましたので、修正したものを掲載させて頂きました。

URLをつける場合

コード: 全て選択

<!-- 都道府県のリストを出力 -->
<xsl:for-each select="$preflist">
<a href="prefsample.php?pref={@id}">
<xsl:value-of select="text()" />
</a>
<xsl:variable name="prefid" select="@id" />
(<xsl:value-of select="count($entryprefs[text()=$prefid])" />)<br />
</xsl:for-each>
 もちろん、件数部分もリンクに含めてしまってもOKです。

URLをつける場合

コード: 全て選択

<!-- 都道府県のリストを出力 -->
<xsl:for-each select="$preflist">
<a href="prefsample.php?pref={@id}">
<xsl:value-of select="text()" />
<xsl:variable name="prefid" select="@id" />
(<xsl:value-of select="count($entryprefs[text()=$prefid])" />)
</a><br />
</xsl:for-each>

h1ro
記事: 6
登録日時: 2006年9月26日(火) 17:51

投稿記事 by h1ro » 2006年10月06日(金) 21:33

webmasterさま、tsuさま
この度は色々とご教授いただきましてありがとうございました!

動きの点で、ひとつ難しそうなポイントがあったのでご報告させていただきます。
遷移が、『項目リスト』→『一覧ページの表示のみ』であれば、まったく問題がないのですが、以下の様な動きには対応不可でしょうか?


全ページに表示されるメニューとして

リスト1 (4)
リスト2 (3)
リスト3 (2)

とカテゴリー表示をしたいとします。


そして例えば リスト1(4) を選択し、一覧表示ページにいくと、
メニューリストの項目が

リスト1 (4)
リスト2 (0)
リスト3 (0)

というように、なってしまいます。。

カテゴリーページに飛ぶと、既にエントリーが絞られている状態であり、
entrylistにはいってくるエントリー総数がそうなってしまう為だと認識しております。

この様に絞込みをかけられているページから、エントリーの総数を参照するような方法はございますでしょうか?
Hackワザに対するさらなる要望みたいで、誠に申し訳ございません。。

つたない説明で恐縮ですが、何卒よろしくお願いいたします。

webmaster
Site Admin
記事: 1451
登録日時: 2004年12月10日(金) 10:09

投稿記事 by webmaster » 2006年10月06日(金) 21:55

 h1roさんいつもありがとうございます。

 おそらく全ページに表示されるメニューの表示用の埋め込みタグに、

<cmsd:group name="xxxx" />

 の絞込み指定を追加していると思うのですが、これを外して頂ければ、その部分は
絞込みの影響を受けなくなります。
 ついでに pageno="top" でページも固定してしまえば、ページ遷移の影響も
受けなくなります。

 もし、カテゴリ表示とエントリ一覧表示を同一のデザイン定義で出力している場合は、
それらを分離して、別々の埋め込みタグを使って表示するようにして下さい。

 ご不明な点は再度ご質問頂ければ幸いです。

tsu
パワーユーザー
記事: 208
登録日時: 2006年1月16日(月) 12:00
お住まい: さいたま

投稿記事 by tsu » 2006年10月06日(金) 22:55

webmasterさんh1roさんこんばんは。

私はもうそのまま

コード: 全て選択

<cmsd:entrylist name="test" design="cate" />
と出しちゃってます。

h1ro
記事: 6
登録日時: 2006年9月26日(火) 17:51

解決いたしました!

投稿記事 by h1ro » 2006年10月08日(日) 03:06

webmasterさま、tsuさま
いつもありがとうございます。

まさにご指摘どおりでした。。
絞り込み指定がはいってしまっていたので当然だったのですね。

tsuさんのコードでそのままドンピシャです!
ありがとうございました。

h1ro
記事: 6
登録日時: 2006年9月26日(火) 17:51

追記

投稿記事 by h1ro » 2006年10月08日(日) 03:25

ちなみに私のケースでは、

webmasterさまご指摘の属性を加えた、
ページ遷移後のカウントにも影響が出ない形の
<cmsd:entrylist name="test" design="cate" pageno="top"/>
で解決いたしました。

ご報告まで。

返信