近デジ PDFダウンロード 「目次」に対応してみた。

 くらひと様にコメントでご質問頂いた内容に関して、自分の備忘もかねて補足しておく。

 というかホントのことをいうと、「目次」に関してはまったく頭が及んでなかったので、コメントに答える為に改めて見直してみたら「あ。できそうじゃん」と思い、ウソいってたらマズイので実際に試してみたというわけだ(笑)。

 最終コマ数を取得する為に、ビューワの画面をGETしていることは既に述べたが、このページの中にはいわゆる「書誌情報」も含まれている。
 また、このページはJavaScriptでインタフェイスを切り替えているだけで、元々1ページの中に目次の情報も含まれている。

 例によって
 http://kindai.ndl.go.jp/info:ndljp/pid/1137942
 このページをサンプルとする。

 ブラウザでこのページのソースを表示してみると、そのhtmlの中に書誌情報・目次情報が埋め込まれていることが確認できる。

 コメントにも書いたとおり;
  •  「タイトル」・「著者」・「出版者」などは、 class="simple-metadata-list"(簡易表示用)あるいは class="detail-metadata-list"(詳細表示用)とタグ付けされたブロックに含まれている。
  •  「目次情報」(がある場合)は、class="ndltree" の下に存在する。この class の startContentNo が目次のコマ数、title が目次見だしのようである。

PDFにはいくつか「目次」処理を行う手段があるが、ここではもっともノーマルな「しおり」(ブックマーク)として、ndltreeのデータを使うことを考えてみる。

 何度も繰り返しているように、本来はこの部分は構造化文書(html.xml)としてきちんとパースするべきである。だが、残念なことに.netではいわゆる「HTMLパーサー」が標準としては提供されていない。
 もちろんxmlパーサーで代用することは可能だろうが、近デジの提供するhtmlがvalidなxmlとして解釈できるかどうかがよく判らないし、真面目にhtmlパーサーを作ると面倒くさいので(爆)、例によって単なる検索で処理してみた。

 まずはndltreeブロックの明細行を検索して、PDFの「しおり」付けに必要な最低限のデータ、startContentNoとtitleを取得する。

 下の例で、DocHtmlは近デジ画面からGETしたhtmlを格納したString、TocInfoは startContentNo,Title のリストである。



string Reg_anchor =
"<input type=\"hidden\" value=\"startContentNo=(?<startContentNo>.*?)\".+?class=\"ndltree-item ndltree-label\" title=\"(?<title>.*?)\">";
Regex re = new Regex(Reg_anchor, RegexOptions.IgnoreCase | RegexOptions.Singleline);
System.Text.RegularExpressions.MatchCollection mc = re.Matches(DocHtml);

foreach (System.Text.RegularExpressions.Match m in mc) {
startNo = m.Groups["startContentNo"].Value;
Title = m.Groups["title"].Value;
TocInfo.Add(TocStartNo, TocTitle);
}



 こんな感じで、startContentNoとtitleのリストを作る。

 今回、俺はiTextSharpを使っているのだが、「しおり」生成のところはかなり悩んだ―というか、書き方がよく判らなかった(今でもよく判ってない)のだが、PDFを結合するために使用しているPdfCopyオブジェクトに、以下のダンドリで処理するとなんとなく上手くいくようである。

訂正:PdfDestinationの値がおかしかったので直した。ズームを継承するにはマイナスを入れろとドキュメントに書いてあるのでそのとおりにしたが、これでホントに良いのかどうか不安だなぁ。


ここで、copyオブジェクトはPdfCopy型であり、個別PDFを結合した結果が格納されている。


PdfContentByte cb = copy.DirectContent;
PdfOutline root = cb.RootOutline;
PdfWriter wrt = cb.PdfWriter;
wrt.ViewerPreferences = PdfWriter.PageModeUseOutlines; // しおり開くモード
PdfOutline oline = new PdfOutline(root, PdfAction.GotoLocalPage(1, new PdfDestination(0,-1.0F,-1.0F,0.0F), wrt), "Contents");
for (int i = 0; i < TocInfo.Count; i++) {
PdfOutline oline1 = new PdfOutline(oline, PdfAction.GotoLocalPage(TocInfo[i].startNo, new PdfDestination(0,-1.0F,-1.0F,0.0F), wrt), TocInfo[i].title);
}



 ようするに、copyオブジェクトのRootOutlineを取得し、そこにまずトップノード(ここでは"Contents"という見だし)を作成し、その配下(section)として、取得した目次情報を追加してしまう、という処理である。
 PDFのOutlineオブジェクトはもちろん階層化できるのだが、どうも近デジデータはフラットなデータのようなので、そのまんまループで回して貼り付けている。

 ということで、この機能を追加してはき出させたPDFはこうなる。
c0071416_13474395.jpg


 なるほど、ある種の文書はやっぱり目次があった方が圧倒的に便利である。

 気づかせて下さった くらひと様 に感謝申し上げます。
[PR]
by signal-9 | 2012-07-24 13:49 | TIPSとかKludgeとか
<< 【近デジ漁り】「異史」本いろいろ 近デジ PDF一括ダウンローダ... >>