インデックステンプレートで記事をカテゴリ別/年別に出力する

先日、MTQに以下のような質問がありました。

インデックステンプレートで、記事一覧を以下のような形で出力したい。

カテゴリ名A
 2013年
  記事タイトル1
  記事タイトル2
 2012年
  記事タイトル3
  記事タイトル4
カテゴリ名B
 2013年
  記事タイトル5
 2012年
  記事タイトル6

この質問に答えます。

1.MTTopLevelCategoriesタグとMTArchiveListタグを組み合わせる

記事の数が少なくて、すべての記事を出力しても再構築にあまり時間がかからない場合は、MTTopLevelCategoriesタグとMTArchiveListタグを組み合わせると簡単です。
MTTopLevelCategoriesタグでカテゴリを順に読み込み、それぞれのカテゴリに対して<mtArchiveList archive_type="Category-Yearly">のタグでカテゴリ年別アーカイブを出力します。
例えば、以下のようにテンプレートを組みます。

<mt:TopLevelCategories>
  <h2><$mt:CategoryLabel$></h2>
  <div>
    <mt:ArchiveList archive_type="Category-Yearly">
      <mt:ArchiveListHeader><ul></mt:ArchiveListHeader>
      <li><$mt:ArchiveDate format="%Y年">
      <mt:Entries>
        <mt:EntriesHeader><ul></mt:EntriesHeader>
        <li><$mt:EntryTitle$></li>
        <mt:EntriesFooter></ul></mt:EntriesFooter>
      </mt:Entries>
      </li>
      <mt:ArchiveListFooter><ul></mt:ArchiveListFooter>
    </mt:ArchiveList>
  </div>
  <$mt:SubCatsRecurse$>
</mt:TopLevelCategories>

2.記事数を限定する

記事の数が多くなると、記事を全件出力すると再構築に時間がかかるようになります。
このような場合、各カテゴリの記事数を制限して再構築の時間を抑えたいです。

2-1.理想的にはこう組みたい

最終的に得たい出力を一般化すると、以下のような形です。

カテゴリAの先頭
  カテゴリAの記事全体の先頭
    ○○年の先頭
      ○○年の記事1
      ○○年の記事2
      ...
      ○○年の記事n
    ○○年の最後
    □□年の先頭
      □□年の記事1
      □□年の記事2
      ...
      □□年の記事n
    □□年の最後
    ...
  カテゴリAの記事全体の最後
カテゴリAの最後
カテゴリBの先頭
  ...
カテゴリBの最後
...

そこで、理想的には、以下の形でテンプレートを組めると分かりやすいです。

<mt:TopLevelCategories>
  カテゴリの先頭の出力
  <mt:Entries lastn="各カテゴリの記事数">
    <mt:EntriesHeader>
      カテゴリの最初の記事の時に出力する内容
    </mt:EntriesHeader>
    <年の最初の記事?>
      年の最初の記事の時に出力する内容
    </年の最初の記事?>
    個々の記事の出力
    <年の最後の記事?>
      年の最後の記事の時に出力する内容
    </年の最後の記事?>
    <mt:EntriesFooter>
      カテゴリの最後の記事の時に出力する内容
    </mt:EntriesFooter>
  </mt:Entries>
  <$mt:SubCatsRecurse$>
  カテゴリの最後の出力
</mt:TopLevelCategories>

2-2.年の最初/最後の記事の判断

上記のようにテンプレートを組もうとすると、年の最初の記事と最後の記事を判断する必要があります。

年の最初の記事かどうかは、「1つ前の記事の年と、今処理している記事の年が異なる」という条件で判断することができます。
記事を出力するたびにその記事の年を変数に保存しておくことで、上記のような処理を行うことができます。

一方、年の最後の記事かどうかは、「1つ次の記事の年と、今処理している記事の年が異なる」という条件で判断します。
ところが、MTEntriesタグの繰り返しでは、同一カテゴリ内での1つ次の記事を、確実に先読みすることができません。

2-1.の最終的に得たい出力を見ると、「□□年の最初」の直前に「○○年の最後」が来ています。
同様に、ある年の最初の部分の直前に、その前の年の最後の部分が来ます。

そこで、条件判断の方法を変えて、年の最初の記事の条件を満たしたら、その前年の記事の最後の部分と、年の最初の記事の部分を出力するようにします。
そして、年の最後の条件判断を削除します。

ただ、このようにすると、一番最初の記事の時にも、前年の記事の最後の部分が出力されてしまいます。
一方、一番最後の記事の後に、年の最後の部分が出力されません。

そこで、年の最初の記事であっても、一番最初の記事の時には、前年の最後の記事の部分を出力しないようにします。
一方、一番最後の記事の時には、年の最後の最後の部分を出力するようにします。
具体的には、テンプレートを以下のように組み替えます。

<mt:TopLevelCategories>
  カテゴリの情報の出力
  <mt:Entries lastn="各カテゴリの記事数">
    <mt:EntriesHeader>
      カテゴリの最初の記事の時に出力する内容
    </mt:EntriesHeader>
    <年の最初の記事?>
      <mt:EntriesHeader>
      <mt:Else>
        年の最後の記事の時に出力する内容
      </mt:EntriesHeader>
      年の最初の記事の時に出力する内容
    </年の最初の記事?>
    個々の記事の出力
    <mt:EntriesFooter>
      年の最後の記事の時に出力する内容
      カテゴリの最後の記事の時に出力する内容
    </mt:EntriesFooter>
  </mt:Entries>
  <$mt:SubCatsRecurse$>
</mt:TopLevelCategories>

2-3.事例

ここまでの話に基づいて、各カテゴリの最新記事5件を年毎に出力する例を作ると、以下のようになります。

<mt:TopLevelCategories>
  <h2><$mt:CategoryLabel$></h2>
  <$mt:SetVar name="last_year" value="-1"$>
  <mt:Entries lastn="5">
    <mt:EntriesHeader>
      <div>
      <ul>
    </mt:EntriesHeader>
    <$mt:EntryDate format="%Y" setvar="entry_year"$>
    <mt:If name="last_year" ne="$entry_year">
      <mt:EntriesHeader>
      <mt:Else>
        </ul>
        </li>
      </mt:EntriesHeader>
      <li><$mt:EntryDate format="%Y年"$>
      <ul>
      <$mt:SetVar name="last_year" value="$entry_year"$>
    </mt:If>
    <li><$mt:EntryTitle$></li>
    <mt:EntriesFooter>
        </ul>
        </li>
      </ul>
      </div>
    </mt:EntriesFooter>
  </mt:Entries>
  <mt:SubCatsRecurse>
</mt:TopLevelCategories>

このテンプレートでは、前の記事の年を、変数last_yearに保存するものとしています。
9行目のMTEntryDateタグで、現在の記事の年を変数entry_yearに代入します。
そして、10行目のMTIfタグで、変数last_year(=前の記事の年)と変数entry_year(=現在の記事の年)を比較して、年の最初の記事かどうかを判断します。
また、記事を出力した後に、繰り返しの次の回に備えて、変数last_yearに現在の記事の年を代入します(18行目)。

なお、最初の記事の時点では、変数last_yearにはまだ値がない状態になります。
そこで、MTEntriesタグの繰り返しに入る前に、最初の記事の年と一致しない値として-1を代入しておき、最初の記事での条件判断が正しく行われるようにします(3行目)。