大変お待たせして申し訳ありません。書籍「実践 F#」の正誤表が公開されました。出版社サポート サイトで重要な訂正を、荒井さんのブログでさらなる訂正を公開している形となります。
すべての正誤情報を保存・印刷するには不便な格好での公開となり、重ねてお詫び申し上げます。つきましては、正誤情報をスクレイピングして以下画像のような HTML にまとめて整形する F# スクリプトをご用意いたしました。
F# スクリプトは以下の通りです。使用方法は、スクリプトの先頭のコメントに記載しております。よろしければお使いください。
(**
* PracticalFS.Errata.fsx
* by @igeta
*
* 書籍「実践 F#」の正誤表を取得する F# スクリプトです(以下、本スクリプト)。
*
* 本スクリプトは、Html Agility Pack ライブラリ(ライセンス:Ms-PL)を使用
* します。Html Agility Pack は、http://htmlagilitypack.codeplex.com/ から
* HtmlAgilityPack.1.4.0.zip をダウンロードして使用します。
*
* 本スクリプトを使用するには、まず適当なフォルダーを作成し、その中に本スク
* リプトと、HtmlAgilityPack.1.4.0.zip を解凍してできる HtmlAgilityPack.dll
* など3つのファイルを格納します。
*
* HtmlAgilityPack.dll
* HtmlAgilityPack.pdb
* HtmlAgilityPack.XML
* PracticalFS.Errata.fsx
*
* そして、「fsi PracticalFS.Errata.fsx」コマンドを実行して、同フォルダー内に
* Errata.html を生成します。
*)
#r@"HtmlAgilityPack.dll"
module Scraper =
open System
open HtmlAgilityPack
let readUri (addr: Uri) =
use webc = new System.Net.WebClient()
let strm = webc.OpenRead(addr)
new System.IO.StreamReader(strm)
let loadHtml (addr: Uri) =
let html = HtmlDocument()
use reader = readUri addr
html.Load reader
html.DocumentNode
let selectNode xpath (node: HtmlNode) =
node.SelectSingleNode(xpath)
let selectNodes xpath (node: HtmlNode) =
node.SelectNodes(xpath)
let getHtml (node: HtmlNode) = node.InnerHtml
let getText (node: HtmlNode) = HtmlEntity.DeEntitize(node.InnerText)
module Program =
open System
open System.IO
open System.Text.RegularExpressions
open Scraper
let split (s:string) =
s.Split([|' '; ' '|], StringSplitOptions.RemoveEmptyEntries)
let pageNum s =
String.Format("{0:000}", Regex.Replace(s, "[^\d]*(\d+).*", "$1") |> int)
let correct s =
Regex.Replace(s, @"<strong>", @"<strong style=""color: #c00;"">")
let ssget ss i =
if i < Array.length ss then ss.[i] else""
let fromGihyo () =
Uri(@"http://gihyo.jp/book/2011/978-4-7741-4516-7/support")
|> loadHtml
|> selectNode @"//div[@class='readingContent01']"
let fromMsdnB () =
Uri(@"http://blogs.msdn.com/b/shozoa/archive/2012/02/29/practical-f-errata1.aspx")
|> loadHtml
|> selectNode @"//div[@id='ctl00_content_ctl00_content']//div[contains(@class, 'post-content')]"
let getRecords root =
Seq.zip (selectNodes @"./h4" root)
(selectNodes @"./table/tbody" root)
|> Seq.map (fun (h4, tb) ->
let xs = h4 |> getText |> split
let ys = tb |> selectNodes @"./tr/td" |> Seq.map getHtml |> Seq.toArray
[| pageNum (ssget xs 0); ssget xs 1; ssget ys 0; correct (ssget ys 1) |])
let writeTable (writer: StreamWriter) =
writer.WriteLine(@"<table>")
writer.WriteLine(@"<tr><th style=""width: 36px;"">Page</th><th style=""width: 98px;"">見出し</th><th>誤</th><th>正</th></tr>")
let xs, ys = getRecords <| fromGihyo (),
getRecords <| fromMsdnB ()
Seq.append xs ys
|> Seq.sort
|> Seq.iter (fun arr ->
writer.WriteLine("<tr>" + String.concat "" (Array.map (fun s ->"<td>"+s+"</td>") arr) + "</tr>"))
writer.WriteLine(@"</table>")
let main () =
let path = Path.Combine(__SOURCE_DIRECTORY__, "Errata.html")
use writer = new StreamWriter(path)
writer.WriteLine(@"<!DOCTYPE html>")
writer.WriteLine(@"<html>")
writer.WriteLine(@"<head>")
writer.WriteLine(@"<title>実践F# 関数型プログラミング入門(正誤表)</title>")
writer.WriteLine(@"<meta http-equiv=""Content-Type"" content=""text/html; charset=utf-8"">")
writer.WriteLine(@"<style>")
writer.WriteLine(@" table { border-collapse: collapse; empty-cells:show; }")
writer.WriteLine(@" th, td { border: solid 2px #ccc; }")
writer.WriteLine(@"</style>")
writer.WriteLine(@"</head>")
writer.WriteLine(@"<body>")
writeTable writer
writer.WriteLine(@"</body>")
writer.WriteLine(@"</html>")
do main ()