次へ: 4 パターン部分の定義 上へ: AWK による HTML ファイルの整形 その 3 前へ: 2 前回作成した一覧ファイル (PDF ファイル: awkwww3.pdf)


3 全体のおおまかな構造

今回は、必要な行の取得は 単に <li> で始まる行を拾っていけばいいだけですが、 それを分類して出力しなければいけません。 しかし、取得した時点でそれがどのジャンルに入るかは分類できたとしても、 その時点で出力してしまったのではやはり時系列順に出てしまうことになりますので、 各ジャンル毎に、
そのジャンルの 1 番目の記事、そのジャンルの 2 番目の記事、...
のように記事を保存しておく必要があります。 そして記事全体を読み込んだ後で、 それらを各ジャンル毎に出力していくことになります。

つまり、ジャンルが複数存在して、 その各ジャンルに記事が複数存在することになりますが、 そのような記事の保存のために 2 次元配列を利用することにします。

2 次元配列とは、 配列の添え字を 2 次元的に添え字付けられる配列のことを言います。 AWK の配列は連想配列で、元々添え字に文字列も使用できますから、 それを工夫すれば、例えば

a["1x1"], a["1x2"], a["2x1"], a["2x2"], ...
のようにして容易に 2 次元配列を作れるのですが、 AWK 自体に 2 次元配列という仕組みが用意されています。

C 言語では、2 次元配列は a[i][j] のように表現しますが、 AWK の 2 次元配列は a[i, j] のように添え字を `,' で区切って 指定することになっています1

よって、例えば、

a[1,1]=1; a[1,2]=2; a[2,1]="x"; a[2,2]="y";
のような使い方ができますし、文字列を添え字にして、
a["abc",3]=3
のように使うこともできます。

今回は「$i$ 番目のジャンルの $j$ 番目の記事」を hs[i,j] という 2 次元配列に保存することにします。

これを利用すれば、全体の AWK の疑似コードは以下のように書けます。

  BEGIN{
      # NG = 全ジャンル数
      # hn[j] = j 番目のジャンルの記事数 (1<=j<=NG+1)
      #    ((NG+1) 番目のジャンルは「その他」の記事)
      # hs[j,k] = j 番目のジャンルの記事を保存する配列 (1<=k<=hn[j])
      #  
      # (1) j 番目のジャンルのキーワードパターン pat[j] を定義 (1<=j<=NG)
  }
  ($0 ~ /^<h2>/){ title=$0 }
  ($0 ~ /^<li>/){
      # (2) <li> 行から見出し部分だけを取り出す (= str とする)
      for(j=1;j<=NG;j++){
          if(str ~ pat[j]){
              hn[j]++
              hs[j, hn[j]]=$0
              break
          }
      }
      if(j>=NG+1){   # いずれにもマッチしなかった場合。この場合 j==NG+1
          hn[j]++
          hs[j, hn[j]]=$0  # 「その他」に保存
      }
  }
  END{
      # (3) HTML ヘッダ等の出力
      for(j=1;j<=NG+1;j++){
           # (4) hs[j,1] ~ hs[j,hn[j]] を出力
      }
      # (5) HTML フッタ等を出力
  }

($0 ~ /^<h2>/)<h2> で始まる行に対する処理を意味しますが、 これは /^<h2>/ とだけ書いても構いません。 この見出しの行は title という変数に保存していて、 最後のヘッダの出力で利用します。

($0 ~ /^<li>/) のブロックが 各 <li> 行に対する処理ですが、この中で各ジャンルにマッチするかをパターンマッチングを利用して調べています。 正規表現とのパターンマッチングは、通常は

  str ~ /正規表現/
のように書くのですが、正規表現をあらかじめ変数 (例えば $pat$) に保存しておいて、
  str ~ pat
のように書いても同じことになります2。 この場合、変数 $pat$ に代入する文字列には / / は含めませんし、 str ~ /pat/ とも書きません。 逆に str ~ /pat/ と書いてしまうと ``pat'' という文字列とのマッチングを意味してしまいます。

パターンにマッチした場合には、 そのジャンルに追加するために記事数を一つ増やし (hn[j]++)、 記事をそのジャンルの配列の最後に追加しています (hs[j,hn[j]]=str)。 その場合はそれ以降のマッチングをやる必要がないので break で for 文を抜けだしています。

いずれにもマッチしなかった場合は、for 文のインデックスの $j$$(NG+1)$ になっているはずですから、 それを判別してその $(NG+1)$ 番目のジャンル (「その他」) に保存しています。

なお、for 文の breaknext に置きかえれば、 いずれかにマッチすれば for 文の次の文に進むことはなくなりますので、 この for 文の次の if によるチェックは必要ありません。 今回も最終的にはそのように書くことにします。

(3) や (5) のヘッダやフッタの出力は、 [4] や [5] と同様の関数を使って簡単に出力できます。 (2) の部分も、[4] や [5] で述べたように、 sub() などを使って容易に取り出すことができます。 (4) の部分もほぼそのまま並べるだけなので、 よって考えるべき部分は (1) のみ、となります。


次へ: 4 パターン部分の定義 上へ: AWK による HTML ファイルの整形 その 3 前へ: 2 前回作成した一覧ファイル
竹野茂治@新潟工科大学
2006年9月8日