Archive for January, 2009

PDFの検索結果に画像でスニペットを表示する実験

Jan 19 2009 Published by funaki under いじる

お蔵入りしていたコード。
PDFを検索したときの結果には画像でスニペットを返した方が見やすいかも、というアイディアを実現しようとしたもの。なぜ画像の方がいいかというと検索語近辺のテキストをスニペットにしても、表だったりPDFの中で文字列が表示される順に記述されていなかったときに脈絡がなくて役に立たないことがあるから。

さて、デモコードはまずユーザからURLで指定されたPDFファイルをサーバでダウンロード、テキストを抽出する。テキストにはPDF上での位置情報が付与されているので、それを含めてJSON形式でJavaScriptのコードに直接出力。文字列の検索はJavaScriptでやって、ハイライトは先の位置情報から画像の上にCSSで乗せる。

現状では、重い、PNGへの変換精度がいまいち、検索の機能が貧弱、等々実用化するまでには問題がたくさんある。

PDFからテキストを抽出、PNGに変換するところにはいくつか既存のアプリを使っている。

<?php
require_once 'Zend/Uri.php';

define('PDF_SAVE_PATH', '/tmp/pdfsearch/');
define('PNG_SAVE_PATH', '/var/www/zuzara.org/pdfsearch/png/');

dl('json.so');
$errorMessage = '';

$pdfUrl = (isset($_GET['pdfurl']) ? $_GET['pdfurl'] : '');
$seed   = '';

$images = array();
$data = array();

if ($pdfUrl != '') {
    try {
        $uri = Zend_Uri::factory($pdfUrl);
        if ($uri->valid() === true) {
            $seed = md5($pdfUrl);
            $pdfFile = PDF_SAVE_PATH . $seed . '.pdf';
            $txtFile = PDF_SAVE_PATH . "$seed.txt";
            if (!file_exists($pdfFile)) {
                `wget -q -O $pdfFile "$pdfUrl"`;
                `pdftoppm -f 1 -l 1 $pdfFile $pdfFile`;
                `mogrify -format png $pdfFile-*`;

                $cmd = "$pdfFile-*.png " . PNG_SAVE_PATH;
                `mv $cmd`;

                `/usr/local/src/xpdf-3.02/xpdf/pdftotext $pdfFile -f 1 -l 1 -cfg /etc/xpdfrc -layout -enc UTF-8 >$txtFile`;

                $buf = file_get_contents($txtFile);
                $frags = explode('*** line fragments ***', $buf);
                foreach ($frags as $i => $frag) {
                    $d = preg_match_all('@x=([\d\.]+)\.\.([\d\.]+) y=([\d\.]+)\.\.([\d\.]+) base=[\d\.]+ \', text: (.*?)\'@s', $frag, $m);
                    $data[$i] = array();
                    for ($j = 0; $j < $d; $j++) {
                        //$data[$i][] = array('text' => $m[5][$j], 'xMin' => $m[1][$j], 'xMax' => $m[2][$j], 'yMin' => $m[3][$j], 'yMax' => $m[4][$j], );
                        for ($k = 1; $k <= 4; $k++) {
                            $m[$k][$j] = intval($m[$k][$j]) * 2.084;
                        }
                        $data[$i][] = array('text' => $m[5][$j], 'top' => $m[3][$j], 'left' => $m[1][$j], 'width' => $m[2][$j] - $m[1][$j], 'height' => $m[4][$j] - $m[3][$j], );
                    }
                }
                file_put_contents($txtFile, serialize($data));
            } else {
                $data = unserialize(file_get_contents($txtFile));
            }
            foreach (new DirectoryIterator(PNG_SAVE_PATH) as $file) {
                if (strpos($file->getFilename(), $seed) !== false) {
                    $images[] = $file->getFilename();
                }
            }
            sort($images);
        } else {
            $pdfUrl = '';
        }
    } catch (Zend_Uri_Exception $e) {
        $errorMessage = $e->getMessage();
    }
}
?>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>pdfsearch</title>
<script type="text/javascript" src="jquery-1.2.6.min.js"></script>
<script type="text/javascript" src="jquery.dimensions.js"></script>
<script type="text/javascript">
data = <?php echo json_encode($data); ?>;
$(function() {
    $('#searchbox').submit(function() {
        var query = $(':text').val();
        var matched = 0;
        jQuery.each(data, function(i) {
                jQuery.each(data[i], function(j) {
                        if (data[i][j].text.match(query)) {
                            var t = $('#p-0').position().top + data[i][j].top;
                            $('#highlight').css({ top: t,
                                                  left: ($('#p-0').position().left + data[i][j].left),
                                                  width: data[i][j].width,
                                                  height: data[i][j].height,
                                                  });
                            $(window).scrollTop(t - 50);
                            $('#highlight').show('fast');
                            matched++;
                            return;
                        }
                    }
                );
            }
        );
        if (matched == 0) {
            alert("did not match.");
        }
        return false;
    });
});
</script>
<link rel="stylesheet" href="style.css" type="text/css" media="screen" />
</head>
<body>
<h1>pdfsearch</h1>

<?php if ($errorMessage != ''): ?>
<div id="errormessage"><?php echo $errorMessage; ?></div>
<?php endif; ?>

<?php if (count($images) > 0): ?>
<form id="searchbox" action="" method="GET">
<input type="text" name="search" />
<input type="submit" value="submit" />
</form>
<?php endif; ?>

<?php foreach ($images as $p => $image): ?>
<h2>page: <?php echo $p+1; ?></h2>
<img id="p-<?php echo $p; ?>" src="png/<?php echo $image; ?>" alt="" />
<?php break; /*XXX*/ endforeach; ?>

<form id="submitpdfurl" action="" method="GET">
pdf url:&nbsp;
<input type="text" id="pdfurl" name="pdfurl" value="<?php echo htmlspecialchars($pdfUrl, ENT_QUOTES); ?>" />
<input type="submit" value="Convert!" />
</form>

<div id="highlight"></div>

</body>
</html>

No responses yet

PukiWikiにHTTP経由でデータをインポートする

Jan 18 2009 Published by funaki under いじる

紙copi のデータを PukiWiki に移行しようと思って適当なスクリプトを書いた。
wiki フォーマットで書いていないので見た目は乱れるがMacを使うことが多くなったのでPukiWiki一本にまとめることにした。

<?php
/**
 *  PukiWiki HTTP API
 *
 *  @since  2009.1.18
 */
require_once 'Zend/Http/Client.php';

class Pukiwiki_API
{
    const DEFAULT_ENCODING = 'EUC-JP';

    private $_http;

    public function __construct($serverName, $httpAuthUserName, $httpAuthPassword)
    {
        $this->_http = new Zend_Http_Client($serverName, array('keepalive' => true));
        $this->_http->setAuth($httpAuthUserName, $httpAuthPassword);
    }

    public function write($title, $body)
    {
        $this->_http->setParameterPost(array(
                        'encode_hint' => $this->encode('ぷ'),
                        'template_page' => '',
                        'cmd' => 'edit',
                        'page' => $this->encode($title),
                        'digest' => md5(''),
                        'msg' => $this->encode($body),
                        'write' => $this->encode('ページの更新'),
                        'notimestamp' => 'true', // 効いてる??
                        'original' => $this->encode($body),
                    ));
        $response = $this->_http->request('POST');
        //echo mb_convert_encoding($response->getBody(), 'UTF-8', self::DEFAULT_ENCODING);
    }

    private function encode($str)
    {
        return mb_convert_encoding($str, self::DEFAULT_ENCODING, 'UTF-8');
    }
}

呼び出し側は

<?php
require_once 'Pukiwiki_API.php';

$p = new Pukiwiki_API('http://PukiWikiを設置したサーバ/', 'ベーシック認証用のユーザ名', $argv[1]);

$path = realpath($argv[2]); // 紙copi のディレクトリを指定
$pathLen = strlen($path);
$objects = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path), RecursiveIteratorIterator::SELF_FIRST);
foreach($objects as $name => $object) {
    if ($object->isFile()) {
        $pageName = "kami" . substr($name, $pathLen);
        $body = mb_convert_encoding(file_get_contents($name), 'UTF-8', 'SJIS');
        echo $pageName, "\n";
        $p->write($pageName, $body);
    }
}

No responses yet

ムームードメインDNSカスタム設定 + Google Apps

Jan 09 2009 Published by funaki under いじる

先月からムームードメインがようやくDNSをいじれるようになった。大歓迎。
で、全部無料なのが怖いぐらいGoogleに依存しているのでGoogle Appsを使うことにした。

ムームーDNS:カスタム設定でレコード情報を変更 – NEO-SHOCKER.COM
ここに書いてある内容で網羅されているが一応設定を書いておくと、

MX ASPMX.L.GOOGLE.COM 10
MX ALT1.ASPMX.L.GOOGLE.COM 20
MX ALT2.ASPMX.L.GOOGLE.COM 20
MX ASPMX2.GOOGLEMAIL.COM 30
MX ASPMX3.GOOGLEMAIL.COM 30
MX ASPMX4.GOOGLEMAIL.COM 30
MX ASPMX5.GOOGLEMAIL.COM 30
TXT v=spf1 include:aspmx.googlemail.com ~all
mail CNAME ghs.google.com
etc...

最後に、ネームサーバをムームDNSに変更して設定完了です。

この部分を抜かしていてハマった。。48hを過ぎてもAppsはいっこうに使えるようにならず。。

ムームードメイン – ネームサーバ設定変更
このページで「ムームードメインのネームサーバ(ムームーDNS)を使用する 」にチェックを入れて更新すればOK。僕の場合は、カスタム設定をしたつもりがここのチェックがロリポップのままで反映されていなかった模様。
設定後、しばらく待てば反映される。

CNAMEが設定されているかのチェックは
Creating CNAME records – Google Apps for Administrators
ここの「Check the status of your CNAME record」でできる。

GmailのLab機能がGoogle Appsのメールでは使えないようで、ちょっと不便になった。
labelやfilterをエクスポートしてインポートしたいがそれはできないようだ。

iPhoneのiCalとGoogle Calendarの同期にNuevaSyncを使っている。こちらも移行しないといけない。

追記
Appsのカレンダーの同期も簡単になった。
Apps専用の設定でOnにしてから後は普通のGoogle Calendarと同じ
Google Sync via ActiveSync – Google Apps Help

参考:
格安サーバ・ロリポップを使い倒す « zuzara

2 responses so far