Skip to content

Instantly share code, notes, and snippets.

@mike-neck
Last active August 29, 2015 14:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mike-neck/bbc69c32924f35e291ec to your computer and use it in GitHub Desktop.
Save mike-neck/bbc69c32924f35e291ec to your computer and use it in GitHub Desktop.
@sue445 さんのccc_privacy_botのpdfパーサーをgroovyで書きなおしてみた
@Grab('org.jsoup:jsoup:1.8.1')
@Grab('com.itextpdf:itextpdf:5.5.5')
@Grab('org.bouncycastle:bcpkix-jdk15on:1.51')
@Grab('org.bouncycastle:bcprov-jdk15on:1.51')
import org.jsoup.*
import com.itextpdf.text.pdf.*
import com.itextpdf.text.pdf.parser.*
def base = 'http://qa.tsite.jp'
def url = "${base}/faq/show/25129"
// 個人情報提供先企業名の一覧pdfのurlを取得
def con = Jsoup.connect(url)
def doc = con.get()
def link = doc.select("#okw_center div div div div dd a")[0]
// 左シフトで最初に入ったオブジェクトだけ保持するクラス
class Reference<T> {
private T value
private boolean empty = true
void leftShift(T v) {
if(empty) {
value = v
empty = false
}
}
T get(){value}
}
import static java.lang.Math.round
// テキスト情報(テキスト, 左の座標, 上からの座標)
class Text {
final String text
final int left
final int bottom
Text(String t, float l, float b) {
this.text = t
this.left = round(l)
this.bottom = round(b)
}
}
// パーサーから渡される細切れのテキストを再構成する
class TextBuilder {
// X座標だけ取得するための単位ベクトル
static final Vector X = new Vector(1,0,0)
// Y座標だけ取得するための単位ベクトル
static final Vector Y = new Vector(0,1,0)
private final StringWriter sb = new StringWriter()
private Reference<Vector> start = new Reference()
private Vector end
// レフトシフトでビルダーにテキスト情報を渡すことができる
void leftShift(TextRenderInfo info) {
sb << info.text
start << info.baseline.startPoint
end = info.baseline.endPoint
}
// Textオブジェクトを構築
Text get() {
new Text(sb.toString(), start.get().dot(X), end.dot(Y))
}
}
//JSON形式にしたいだけ
class Organization {
static final String DELIM = ','
static final String Q = '"'
final int number
final String name
final String since
final String type
final List<String> users
Organization(List<Text> list) {
def l = list.collect{it.text}
number = l[0] as int
name = l[1]
since = l[2]
def tmp = l[3].split(':')
type = tmp[0]
users = tmp[1].contains(DELIM) ? Arrays.asList(tmp[1].split(DELIM)):[tmp[1]]
}
@Override String toString() {
def l = []
l << "${Q}number${Q}: ${number}"
l << "${Q}organization_name${Q}: ${Q}${name}${Q}"
l << "${Q}since${Q}: ${Q}${since}${Q}"
l << "${Q}type${Q}: ${Q}${type}${Q}"
def u = users.collect{"${Q}${it}${Q}"}.join(',')
l << "${Q}${Q}: [$u]"
"{${l.join(',')}}"
}
}
// パーサーから呼び出されるレンダリングオブジェクト
class Listener implements RenderListener {
//テキストビルダー
private TextBuilder tb
//Textオブジェクトを保持
private List<Text> list = []
//テキストブロック開始
void beginTextBlock(){tb = new TextBuilder()}
//テキストのレンダリング
void renderText(TextRenderInfo info){tb << info}
//テキストブロック終了
void endTextBlock(){list << tb.get()}
//イメージのレンダリング(今回は使わない)
void renderImage(ImageRenderInfo info){}
@Override String toString() {
//エクセルで上揃えとかを統一しない人がいるので
//座標をある程度の範囲でグルーピングするための
//ギミック
//see https://twitter.com/mike_neck/status/573486572202409984
int row = -1
def map = [:]
def tmp = list.collect{it.bottom}.sort{-it}.each {
if(row == -1) {
map.put(it, it)
} else if (row - 4 < it) {
map.put(it, map[row])
} else {
map.put(it, it)
}
row = it
}
def body = list.groupBy{
//上からの座標でグルーピング
map[it.bottom]
}.sort{
//上からの座標で並べ替え
-it.key
}.each{
//内部を左からの座標でソート
it.value.sort{it.left}
}.findAll{
//企業の名前のみ取得
it.value.size() == 4 && it.value[0].text ==~ /\d+/
}.collect{
//JSONにする
new Organization(it.value).toString()
}.join(',')
"[$body]"
}
}
// PdfReaderContentParserのイテレーターがPdfReaderだったのが、ちょっとアレなので、クラスを拡張
PdfReaderContentParser.metaClass.define {
each = {Closure c ->
def p = delegate
def r = delegate.reader
int i = 1
while(i <= r.numberOfPages) {
c(p, i++)
}
return p
}
}
//PdfReaderインスタンスをURLから取得
def rd = new PdfReader(new URL("${base}${link.attr('href')}"))
//PdfReaderContentParserのインスタンスを取得
def ps = new PdfReaderContentParser(rd)
//各ページごとに処理
ps.each {parser, page ->
println (parser.processContent(page, new Listener()))
}
rd.close()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment