daisukeの技術ブログ

AI、機械学習、最適化、Pythonなどについて、技術調査、技術書の理解した内容、ソフトウェア/ツール作成について書いていきます

【解説】ブログカスタマイズ:カテゴリー階層化のソースコードを解析する

下記の記事で書いたように、カテゴリーの階層化のブログパーツは、注目記事のカテゴリーの表示には、おそらく対応できていません。

【解説】はてなブログ:カテゴリを階層化する(補足と問題点を追記) - daisukeの技術ブログ

そこで、提供されているソースコードを解析して(デベロッパーツールで解析する方法も記載する)、簡単に対応できそうなら、注目記事のカテゴリーの表示に対応していきます。

なお、JavaScriptは、ほとんど使ったことがないので、基本的なことをメモしながらやっていきます。

解析した結果、結論としては、現時点では、注目記事のカテゴリーの階層化は、不可能だと思われます。

理由は、カテゴリー階層化のJavaScript、つまり、ユーザが配置できるJavaScriptが実行されるタイミングでは、注目記事はまだ何も書かれていなかったためです。おそらく、ページ表示の最後の方のタイミングで、はてなブログの運営が用意しているプログラムで、そのユーザーの注目記事を特定して、注目記事の内容を書き込んでいると思われます。

何かに使えるときもあるかもしれないので、解析した内容を書いておきます。

参考文献

はじめに

ブログカスタマイズの一覧です。良かったら参考にしてください。

ブログカスタマイズのまとめ

JavaScriptの基本

JavaScriptについては、過去に少し使ったことがあったぐらいで、全く覚えてないので、Webを頼りに基本的な内容をメモしておきます。

JavaScriptの基本

JavaScriptの記述場所
  1. </body>の直前に、<script>JavaScriptのソースコード</script>、もしくは、<script type="text/javascript" src="JavaScriptのファイルパス"></script>で記述する
  2. <head></head>の間に、上記と同じ内容で記述する

しかし、<body>コンテンツ</body>の表示を優先するため、<head></head>に時間がかかる内容は避けた方がいいようです。よって、通常は、「1.」が選択されます。

カテゴリーの階層化のソースコードを解析する

本題のカテゴリーの階層化のソースコードを解析していきます。

JavaScriptの文法は、ほとんど分からないままですが、とりあえず見ていきます。

デベロッパーツールを使って、動かしながら、ソースコードを理解する

まず、minのソースでは、見にくいので、GitHubから、通常のソースコードを入手します。

github.com

public/v1.1に対象のソースコードが入っています。

解析に必要そうなのは、「breadcrumb.js」と「category_archive.js」です。

これらを、テスト用のブログで、フッタに貼り付けていた「breadcrumb.min.js」と「category_archive.min.js」を削除して、貼り付けます。

変更を保存して、ページを表示してみると、普通にカテゴリーの階層化が動作したので、次は、デベロッパーツールを使って、動かしていきます。

適当な記事を表示させておき、デベロッパーツールを起動します。ソースタブを開き、表示させた記事をクリックすると、HTMLソースが表示されるので、見たいところでブレークポイントを設定して、ページをリロードすると、ブレークポイントで停止します。

あとは、ステップイン、ステップオーバー、ステップアウトなどを使って、変数の値を確認しながら、解析していけばOKです。

デベロッパーツールでJavaScriptのデバッグ
デベロッパーツールでJavaScriptのデバッグ

カテゴリーの階層化のソースコードの構成

カテゴリーの階層化のソースコードは、以下の構成です。

  • ヘッダ:fulldisplay.min.cssを追加 → 今回の件には無関係
  • フッタ:以下の3つのファイルを追加
    • https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js → 外部から提供されたライブラリなので解析対象外
    • breadcrumb.min.js → パンくずリストの処理や、カテゴリー名を変換する処理を行っている(今回の解析対象)
    • category_archive.min.js → サイドバーのカテゴリーモジュールの階層化の子カテゴリの開閉処理を行っている(今回の解析対象外)

breadcrumb.min.jsには、5つの関数が実装されています。

ready関数は、ページがロードされたときに呼ばれる関数であり、エントリーポイントです。

記事が表示される場合と、カテゴリー名をクリックしたときに表示されるカテゴリー内の記事一覧(アーカイブ)が表示される場合とで、大きく2つに分かれています。

最初の2つの関数の「remapBreadcrumb関数」と「remapArticleCategory関数」は、記事が表示されるときに実行される関数です。

次の2つの関数の「remapCategoryBreadcrumb関数」と「remapArchiveCategory関数」は、記事一覧が表示されるときに実行される関数です。

以降で、処理される順に、それぞれの関数の概要を説明した後、詳細な解析した内容は、ソースコードにコメントしたものを示します。

ready関数

記事が表示される場合の処理で、記事タイトル下のパンくずリスト(変換前の「ブログカスタマイズ、ブログカスタマイズ-JavaScript」など)を取得して、それを引数にして、remapArticleCategory関数(パンくずリストを子カテゴリーに変換する処理)を呼び出します。なお、記事一覧が表示される場合は、この関数は呼び出されません。

次は、記事一覧が表示される場合の処理で、上と同様にパンくずリストを取得して、それを引数にして、remapCategoryBreadcrumb関数(-で結合された「ブログカスタマイズ-JavaScript」を子カテゴリーだけ「JavaScript」に変換する処理)を呼び出します。その後、remapArchiveCategory関数(各記事に書かれているカテゴリー名を変換する処理)を呼び出します。なお、記事が表示される場合は、この関数は呼び出されません。

$(document).ready(function(){
  var $entry_categories = $('#main-inner > article.entry > div.entry-inner > header > div.entry-categories > a');
  if($entry_categories.length>0) {
    remapArticleCategory($entry_categories);// 記事表示時のパンくずリスト変換処理をコール
  }
  var $archive_entries = $('#main-inner > div.archive-entries');// 普通は1個あるはず
  if($archive_entries.length > 0) {
    $breadcrumb_child = $('#top-box > div.breadcrumb > div.breadcrumb-inner > span.breadcrumb-child:first');// カテゴリー一覧のカテゴリー名を取得 (親 or 親-子 or 親-子-孫)
    breadcrumbs = $breadcrumb_child.find('span').text().split('-');
    remapCategoryBreadcrumb(breadcrumbs);// カテゴリー内の記事一覧表示時のパンくずリスト変換処理をコール

    $archive_entries.each(function(){
      remapArchiveCategory($(this).find('section > div.categories > a'));// sectionタグのカテゴリーのリンクを全て取得し、それを引数として、カテゴリー名変換処理をコール
    });
  }
});
remapBreadcrumb関数

記事が表示される時のパンくずリスト変換処理のサブ関数です。

最初は、カテゴリー名が'-'で結合されています。この関数は、結合した名前でURLを作成し、記事に表示する文字列は、子カテゴリーの名前だけを使っています。

var host = $(location).attr('host');
function remapBreadcrumb(breadcrumb){ // 記事表示時のパンくずリスト変換処理のサブ関数
  new_breadcrumb_html='';
  for(var i=0;i<breadcrumb.length;i++) {
    url_category=[];
    for(var j=0;j<=i;j++) {
      url_category[j]=breadcrumb[j];// 1回目は親のみ、2回目は親, 子、3回目は親, 子, 孫 の配列を作成
    }
    var category_url = 'https://'+host+'/archive/category/'+url_category.join('-');// '-' で結合
    new_breadcrumb_html += '<span class="breadcrumb-child""><a class="breadcrumb-child-link" href="'+category_url+'"><span>'+breadcrumb[i]+'</span></a></span>';// 1回目は親のみ、2回目は親-子、3回目は親-子-孫 のURLを作成
    if(i+1<breadcrumb.length) {
      new_breadcrumb_html += '<span class="breadcrumb-gt"> &gt;</span>';// 親、子、孫の場合、最後だけは「>」を付けない
    }
  }
  if(new_breadcrumb_html != '') {
    $('#top-box > div.breadcrumb > div.breadcrumb-inner > span.breadcrumb-child:first').prop('outerHTML',new_breadcrumb_html);// HTMLを書き換え
  }
}
remapArticleCategory関数

記事が表示される時のメイン関数です。

最初は、一番階層が深い名前を取得して、それを分割し、上で示した、remapBreadcrumb関数を呼び出します。

その後の処理は、引数で渡されたカテゴリー名を上書きしていますが、その後、使われないので、必要ない処理です。公式のパンくずリストの機能が、まだ提供されていなかったときのパンくずリストの機能を流用したとのことなので、最初に作られたときには何かに使われた処理だったのかもしれません。

function remapArticleCategory(categories) { // 記事表示時のパンくずリスト変換処理、引数はカテゴリー名の配列
  var index=0;
  while(typeof categories[index+1] != 'undefined' && categories[index+1].text.includes('-')) {
    index+=1;// 階層化対象のカテゴリーの階層数を求める (例:2階層の場合はindex=1、3階層の場合はindex=2、親1つに対して2個以上の子がいる場合もindex=2以上になる
  }

  breadcrumb_array = categories[index].text.split('-');// 最下層の子のカテゴリー名を分割 (例:親-子-孫の場合は要素3つ、親-子1、親-子2の場合は要素2つ ※後ろが有効になる)
  remapBreadcrumb(breadcrumb_array) // 記事表示時のパンくずリスト変換処理のサブ関数をコール

  category_num = categories.length;
  for(var i=0;i<category_num;i++) {
    category_branch = categories[i].text.split('-');
    categories[i].text=category_branch[category_branch.length-1];// 引数の中身を上書きしてるが、特に必要ない処理のはず
  }
}
remapCategoryBreadcrumb関数

記事一覧が表示される場合に呼び出される処理で、remapBreadcrumb関数とやってることは、だいたい同じであり、パンくずリストのカテゴリー名の変換処理です。

function remapCategoryBreadcrumb(breadcrumb) {
  new_breadcrumb_html='';
  for(var i=0;i<breadcrumb.length;i++) {
    if(i+1<breadcrumb.length) { // 親, 子, 孫 の場合、親と子はtrue、孫はfalse
      url_category=[];
      for(var j=0;j<=i;j++) {
        url_category[j]=breadcrumb[j];// 1回目は親のみ、2回目は親, 子 の配列を作成
      }
      var category_url = 'https://'+host+'/archive/category/'+url_category.join('-');
      new_breadcrumb_html += '<span class="breadcrumb-child"><a class="breadcrumb-child-link" href="'+category_url+'">'+breadcrumb[i]+'</span></a></span>';
      new_breadcrumb_html += '<span class="breadcrumb-gt"> &gt;</span>';
      
    } else {
      new_breadcrumb_html += '<span class="breadcrumb-child">'+breadcrumb[i]+'</span>';
    }
  }
  if(new_breadcrumb_html != '') {
    $('#top-box > div.breadcrumb > div.breadcrumb-inner > span.breadcrumb-child:first').prop('outerHTML',new_breadcrumb_html);
  }
}
remapArchiveCategory関数

記事一覧の各記事のカテゴリー名が配列で渡されます。それを1つずつ最下層の名前に置き換える処理です。

function remapArchiveCategory(categories) {
  for(var i=0;i<categories.length;i++) {
    category_branch = categories[i].text.split('-');// 親-子-孫を分割
    categories[i].text=category_branch[category_branch.length-1];// 最下層の名前を設定
  }
}

category_archive.min.js

各関数の概要は以下の通りです。サイドバーのカテゴリーモジュールを対象とした処理であり、「▶」を押されたときに、階層化されたカテゴリーの子カテゴリーを表示したり、「▼」が押されたときに、表示されてる子カテゴリーを非表示にする機能を実現しています。

  • processArchive関数:サイドバーのカテゴリーモジュール内にあるliタグを再帰的に見ていき、階層化されたカテゴリーの開閉を制御している
  • toggleCategory関数:processArchive関数から呼ばれ、階層化された子カテゴリーの開閉の機能を提供している
  • ready関数:processArchive関数を呼び出す

カテゴリーの階層化のソースコードの解析結果

カテゴリーの階層化のソースコードを、一通り確認したが、やはり、注目記事のカテゴリーについては考慮していないようです。

記事を表示した場合と、カテゴリーをクリックしたときのカテゴリー内の一覧の場合で、2通りの対応が必要になります。

おわりに

最終的な結論は、冒頭に書いた通り、現状は、注目記事のカテゴリーについて、階層化に対応するのは不可能です。

今回は以上です!

最後までお読みいただき、ありがとうございました。