最近开始使用私本管理Plus进行罗德斯收集物的管理(包括但不仅限于书籍)。
其能够实现的功能,已经非常接近自己所期望的管理效果了。
并且通过私本管理GOOUT还可以将资料同步到web上,实在是很方便。
不过由于用到的是CGI程序,并且联动的是不支持UTF-8的jcode.pl,所以对中文的支持不好。希望可以慢慢改进吧。感谢shela提供了支持UTF-8的修改版本,现在没有该问题了。
[8255] Re: 要望 投稿者:shela 投稿日:2017/11/12(Sun) 23:08
GOOUTのUTF8対応ですが、私が改造したものでよければ(添付ファイル)どうぞ。
ファイル名はsearch.cgiに変更してください。特殊なモジュールは使っていないはずなのでPerl 5.8.1以上の環境であればおそらく動くと思います。
また、私本管理でCSVファイルを作成する際、文字コードをUTF8またはUNICODEで保存してください。添付:8255.txt (24KB)
#!/usr/bin/perl
#-----------------------------------------------------------
# 私本管理GOOUT for SmartPhone By:EKAKIN
#-----------------------------------------------------------
#
#-----------------------------------------------------------
# 動作環境
#-----------------------------------------------------------
# Perl 5.8.1 以降。
# Perl 5.8.0 は文字コードの扱いに問題があるため、
# 文字化けを起こす等の不具合が発生する可能性があります
# なお、jcode.plは使用しなくなりました
#-----------------------------------------------------------
#
#-----------------------------------------------------------
# 改版履歴
#-----------------------------------------------------------
#2015.01.10 Ver2.1.0 modified by shela
# 私本管理 Ver.4のバックアップデータ(*.shn,*.csv)に対応
# ISBNのチェックディジット計算のバグ修正
# バックアップデータのヘッダ行を無視するように修正
# 一括画像ローカル保存プラグインに対応
#2015.01.10 Ver2.0.1 検索時、特定の文字列が文字化けして正しく検索できない不具合を修正
# 画像が正しく表示されない不具合を修正
#2011.08.07 Ver2.0.0 改名
# UI変更
#2010.08.11 Ver1.0.0 公開
# 【Thank you for kmzw】
#-----------------------------------------------------------
use strict;
use utf8;
use CGI qw/:standard :netscape/;
use Encode qw/decode_utf8/;
use HTML::Entities qw/encode_entities/;
my $TITLE = "私本管理GOOUT"; # タイトル
my $VERSION = "Ver 2.1.0"; # バージョン
my $SCRIPT_NAME = "$ENV{'SCRIPT_NAME'}"; # CGIファイル名
#-----------------------------------------------------------
# カスタマイズ
#-----------------------------------------------------------
my $PAGE = 20; # 1ページに表示する件数
# 「No Image」画像の場所を指定します
# cgiと同じ場所の場合は「noimage.jpg」と指定します
# @niftyの場合は「http://homepage○.nifty.com/○○○/ディレクトリ/noimage.jpg」で指定します
# ↓このままでも使用できますが、変更される可能性があります
my $NOIMAGE = "http://homepage1.nifty.com/EKAKIN/cgi/goout/noimage.jpg";
# For iPhone
my $ICON_PATH = "http://homepage1.nifty.com/EKAKIN/cgi/goout/iphone_icon.png";
my $LINK_URL = "http://www.amazon.co.jp/gp/aw/d/";
my $AMAZON_TAG = "";
#-----------------------------------------------------------
# 検索ファイル名(フォルダと対になるように追加してください)
#-----------------------------------------------------------
my @FILE = ( "123.csv" );
#-----------------------------------------------------------
# 検索フォルダ名(ファイルと対になるように追加してください)
#-----------------------------------------------------------
my @DIR = ( "デフォルト" );
#===========================================================
# 実行処理
#===========================================================
my %FORM = ();
my %FDATA = ();
my @FDATAindex = ();
# フラグ付きUTF-8文字列をフラグなしにして出力させる
binmode STDOUT, ":utf8";
# フォームの内容取得
&formDataGet;
if ( $ENV{'REQUEST_METHOD'} eq "POST"
|| defined( $FORM{page} ) && $FORM{page} ne "" )
{
# 検索結果画面表示
&htmlResult;
}
else {
# 検索画面表示
&htmlStart;
}
exit;
#-----------------------------------------------------------
# 検索画面表示
#-----------------------------------------------------------
sub htmlStart {
&htmlHeader01;
&htmlSearch; # 検索画面
&htmlFooter01;
}
#-----------------------------------------------------------
# 検索結果表示
#-----------------------------------------------------------
sub htmlResult {
&htmlHeader02;
&runSearch; # 検索処理
&htmlFooter02;
}
#-----------------------------------------------------------
# スタイルシート
#-----------------------------------------------------------
sub Style {
print <<"_EOF_";
<style type="text/css">
/* body 初期設定 */
body {
background: rgb(197,204,211);
font-family: Helvetica;
margin: 0;
padding: 0;
-webkit-user-select: none;
-webkit-text-size-adjust: none;
}
/* a 初期設定 */
a {
font: 14px Helvetica;
color: #42688e;
text-decoration: none;
}
a:hover, a:active {
color: #ff0066;
text-decoration: none;
}
/* Center文字列初期設定 */
div.center {
text-align: center;
margin: 4px 0;
padding: 0;
clear: both;
}
/* header 設定 */
div#header {
background: rgb(109,133,163) repeat-x top;
border-top: 1px solid rgb(205,213,223);
border-bottom: 1px solid rgb(46,55,68);
padding: 5px;
margin: 0;
min-height: 44px;
-webkit-box-sizing: border-box;
}
div#header h1 {
color: #fff;
font: bold 18px/30px Helvetica;
text-shadow: #2d3642 0 -1px 0;
text-align: center;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
padding: 5px 0;
margin: 2px 0 0 0;
top: 0;
}
div#header #backButton {
left: 6px;
right: auto;
color: #fff;
font: bold 10px/20px Helvetica;
text-shadow: #2d3642 0 -1px 0;
text-align: center;
width: 38px;
border-width: 0 8px 0 14px;
border:1px solid #263a51;
padding: 5px 0;
display: block;
position: absolute;
margin-top: -40px;
background: #42688e;
-webkit-border-radius: 4px;
}
/* list 初期設定 */
ul {
color: black;
background: #fff;
border: 1px solid #B4B4B4;
font: bold 17px Helvetica;
padding: 0 10px;
margin: 15px 10px 17px 10px;
-webkit-border-radius: 8px;
}
ul li {
color: #666;
border-top: 1px solid #B4B4B4;
list-style-type: none;
padding: 10px;
}
li:first-child {
border-top: 0;
-webkit-border-top-left-radius: 8px;
-webkit-border-top-right-radius: 8px;
}
li:last-child {
-webkit-border-bottom-left-radius: 8px;
-webkit-border-bottom-right-radius: 8px;
}
/* form 設定 */
ul#form li {
padding: 7px 10px;
text-align: center;
vertical-align: middle;
}
ul#form li input[type="text"], ul li select {
color: #777;
background: #fff;
border: 0;
font: normal 17px Helvetica;
padding: 0;
display: inline-block;
margin-left: 0px;
width: 50%;
-webkit-appearance: textarea;
}
ul#form li select {
text-indent: 0px;
background: transparent no-repeat 100% 3px;
-webkit-appearance: textfield;
margin-left: -6px;
width: 50%;
}
ul#form li input[type="submit"] {
width: 44%;
height : 30px;
}
ul#form li.left {
float: left;
}
ul#form li.right {
float: right;
}
/* result 設定 */
ul#result li.img {
height:80px;
padding-left: 100px;
}
ul#result img.img {
display: inline-block;
width: 60px;
height:80px;
margin: 0 0 0 -75px;
float: left;
-moz-background-size: cover;
background-size: cover;
-webkit-border-radius: 0;
background: #F4FBFE;
padding: 0;
}
ul#result div {
color: #000;
font: 14px Helvetica;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
display: block;
margin: 0;
padding: 0;
}
ul#result a {
}
/* nextpage 設定 */
ul#nextpage {
background: #fff;
clear: both;
height: 44px;
text-align: center;
}
ul#nextpage li.left {
border: 0;
-webkit-border-radius: 0;
width: 33%;
float: left;
margin: 0;
padding: 2px 0 0 0;
}
ul#nextpage li.center {
border: 0;
-webkit-border-radius: 0;
width: 34%;
float: left;
margin: 0;
padding: 11px 0;
}
ul#nextpage li.right {
border: 0;
-webkit-border-radius: 0;
width: 33%;
float: right;
margin: 0;
padding: 2px 0 0 0;
}
ul#nextpage li a {
display: block;
width: 100%;
margin: 0;
padding: 10px 0;
}
/* pages 設定 */
ul#pages {
margin: 4px 4px 4px 0;
border: 0;
-webkit-border-radius: 0;
text-align: center;
}
ul#pages li.now {
border: 1px solid #C0D5DD;
-webkit-border-radius: 0;
width: 30px;
height: 22px;
float: left;
margin: 0 0 1px 1px;
background: #fff;
padding: 4px 0;
}
ul#pages li.other {
border: 1px solid #C0D5DD;
-webkit-border-radius: 0;
width: 30px;
height: 30px;
float: left;
margin: 0 0 1px 1px;
background: #F4FBFE;
padding: 0;
}
ul#pages li a {
display: block;
width: 100%;
margin: 0;
padding: 6px 0;
}
</style>
_EOF_
}
#-----------------------------------------------------------
# HTML ヘッダ初期
#-----------------------------------------------------------
sub htmlHeader01 {
print "Content-type: text/html\n\n";
print <<"_EOF_";
<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.1//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile11.dtd">
<html>
<head>
<title>$TITLE</title>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<meta id="viewport" name="viewport" content="width=320; initial-scale=1.0; maximum-scale=1.0; user-scalable=0;" />
<link rel="apple-touch-icon" href="$ICON_PATH" />
_EOF_
&Style;
print <<"_EOF_";
</head>
<body>
<div id="header">
<h1>$TITLE</h1>
</div>
_EOF_
}
#-----------------------------------------------------------
# HTML ヘッダ検索後
#-----------------------------------------------------------
sub htmlHeader02 {
print "Content-Type: text/html\n\n";
print <<"_EOF_";
<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.1//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile11.dtd">
<html>
<head>
<title>$TITLE</title>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<meta id="viewport" name="viewport" content="width=320; initial-scale=1.0; maximum-scale=1.0; user-scalable=0;" />
<link rel="apple-touch-icon" href="$ICON_PATH" />
<script type="text/javascript">
function send(page) {
var form = document.forms[0];
form.page.value = page;
form.submit();
}
</script>
_EOF_
&Style;
print <<"_EOF_";
</head>
<body>
<div id="header">
<h1>$TITLE</h1>
<a href="$SCRIPT_NAME" id="backButton">検索</a>
</div>
_EOF_
}
#-----------------------------------------------------------
# HTML フッタ初期
#-----------------------------------------------------------
sub htmlFooter01 {
print << "_EOF_";
<div class="center"><a href="http://homepage1.nifty.com/EKAKIN" target="_blank">EKAKIN'S SCRIBBLE PAGE</a></div>
</body>
</html>
_EOF_
}
#-----------------------------------------------------------
# HTML フッタ検索後
#-----------------------------------------------------------
sub htmlFooter02 {
print << "_EOF_";
</body>
</html>
_EOF_
}
#-----------------------------------------------------------
# HTML 検索画面
#-----------------------------------------------------------
sub htmlSearch {
print <<"_EOF_";
<form method="post" action="$SCRIPT_NAME" class="tcardform">
<ul id="form">
<li>ジャンル選択
<select name="file">
_EOF_
for ( my $i = 0; $i <= $#FILE; $i++ ) {
my $genre = encode_entities( $DIR[$i] );
if ( defined( $FORM{file} ) && $i eq $FORM{file} ) {
print "<option value=\"$i\" selected>$genre</option>\n";
}
else {
print "<option value=\"$i\">$genre</option>\n";
}
}
print <<"_EOF_";
</select>›
</li>
<li>購入/未購入
<select name="data20">
_EOF_
my @Purchase = ( "", "購入", "未購入", "その他" );
for ( my $i = 0; $i <= $#Purchase; ++$i ) {
if ( defined( $FDATA{20} ) && $Purchase[$i] eq $FDATA{20} ) {
print
"<option value=\"$Purchase[$i]\" selected>$Purchase[$i]</option>";
}
else {
print "<option value=\"$Purchase[$i]\">$Purchase[$i]</option>";
}
}
print <<"_EOF_";
</select>›
</li>
<li>ISBN
<input type="text" name="data0" mode="numeric" istyle="4" size="18" maxlength="13" value="$FDATA{0}" /> </li>
<li>タイトル
<input type="text" name="data1" class="tcardtext" mode="hiragana" istyle="1" size="18" value="$FDATA{1}" /> </li>
<li>作者
<input type="text" name="data5" class="tcardtext" mode="hiragana" istyle="1" size="18" value="$FDATA{5}" /> </li>
_EOF_
if ( defined( $FORM{ftype} ) && $FORM{ftype} eq "or" ) {
print
"<li><input type=\"radio\" name=\"ftype\" value=\"or\" checked />OR ";
print
"<input type=\"radio\" name=\"ftype\" value=\"and\" />AND";
}
else {
print
"<li><input type=\"radio\" name=\"ftype\" value=\"or\" />OR ";
print
"<input type=\"radio\" name=\"ftype\" value=\"and\" checked />AND";
}
print <<"_EOF_";
<input type="submit" value="検索" class="tcardsubmit" /></li>
</ul>
</form>
<div class="center">
</div>
_EOF_
}
#-----------------------------------------------------------
# 検索内容取得
#-----------------------------------------------------------
sub formDataGet {
# 入力パラメータ取得
foreach my $key (param) {
$key = decode_utf8($key);
my $value = decode_utf8( param($key) );
if ( $key =~ /data(\d+)/ ) {
my $index = $1;
if ( $index eq "0" ) {
# ISBN
$FDATA{$index} = &isbn_change($value);
}
else {
# ISBN以外は小文字にして保持
$FDATA{$index} = lc($value);
}
unless ( $FDATA{$index} eq "" ) {
push( @FDATAindex, $index );
}
}
else {
# ジャンル、検索タイプ(AND/OR)、ページ
$FORM{$key} = $value;
}
}
}
#-----------------------------------------------------------
# 検索処理
#-----------------------------------------------------------
sub runSearch {
# ファイルの読み込み
if ( !open( FH, "<:raw", $FILE[ $FORM{file} ] ) ) {
&error( "エラー",
"私本管理バックアップファイルが読み込めません"
);
return;
}
# 初期ページを設定
if ( !defined( $FORM{page} ) || $FORM{page} eq "" ) {
$FORM{page} = 1;
}
my $maxRec = $FORM{page} * $PAGE; # 表示レコード MAX
my $minRec = $maxRec - $PAGE + 1; # 表示レコード MIN
print "<ul id=\"result\">\n";
my ( $codeSet, $offset );
my $count = 0;
my $flg = 0;
while ( my $line = <FH> ) {
if ( $. == 1 ) {
if ( $line =~ s/^\xEF\xBB\xBF// ) {
$codeSet = "utf8";
$offset = 3;
}
elsif ( $line =~ s/^\xFF\xFE// ) {
$codeSet = "utf16le";
$offset = 2;
}
else {
$codeSet = "cp932";
$offset = 0;
}
# BOMを除いた先頭へ戻って読み直し
seek( FH, $offset, 0 );
binmode FH, "encoding($codeSet):crlf:utf8";
$line = <FH>;
}
chomp($line); # 改行を取り除く
# 「,」区切りでCSVファイル1行読込
my @array = split( ",", $line );
# ヘッダ行を読み飛ばす
if ( $array[0] eq "ISBN" ) {
next;
}
my $found = 0;
# 検索
if ( $#FDATAindex == -1 ) {
# 検索項目が未入力の場合は該当ありと判定
$found = 1;
}
elsif ( $FORM{ftype} eq "or" ) {
# OR検索の場合は最初に一致項目が出た時点で該当ありと判定
$found = 0;
my $data;
for ( my $i = 0; $i <= $#FDATAindex; $i++ ) {
$data = $array[ $FDATAindex[$i] ];
$data = lc($data);
# ISBNは-を削除 x,+,*はXとみなす
if ( $FDATAindex[$i] == 0 ) {
$data = &isbn_change($data);
}
if ( index( $data, $FDATA{ $FDATAindex[$i] } ) > -1 ) {
$found = 1;
last;
}
elsif ( $FDATAindex[$i] == 1 || $FDATAindex[$i] == 5 ) {
# タイトルカナ検索 / 作者カナ検索
my $n = int( $FDATAindex[$i] ) + 1;
$data = $array[$n];
$data = lc($data);
if ( index( $data, $FDATA{ $FDATAindex[$i] } ) > -1 ) {
$found = 1;
last;
}
}
}
}
elsif ( $FORM{ftype} eq "and" ) {
# AND検索の場合は最初に不一致項目が出た時点で該当なしと判定
$found = 1;
my $data;
for ( my $i = 0; $i <= $#FDATAindex; $i++ ) {
$data = $array[ $FDATAindex[$i] ];
$data = lc($data);
# ISBNは-を削除 x、+、*はXとみなす
if ( $FDATAindex[$i] == 0 ) {
$data = &isbn_change($data);
}
if ( index( $data, $FDATA{ $FDATAindex[$i] } ) == -1 ) {
# タイトルカナ検索 / 作者カナ検索
if ( $FDATAindex[$i] == 1 || $FDATAindex[$i] == 5 ) {
my $n = int( $FDATAindex[$i] ) + 1;
$data = $array[$n];
$data = lc($data);
if ( index( $data, $FDATA{ $FDATAindex[$i] } ) == -1 )
{
$found = 0;
last;
}
}
else {
$found = 0;
last;
}
}
}
}
# 検索にヒットした場合
if ($found) {
$count++;
if ( $minRec <= $count && $maxRec >= $count ) {
# ISBN
$array[0] =~ s/-//g; # ハイフンを除去
$array[0] =~ s/^978|^979//g; # 先頭に978か979があれば(ISBN13)除去
$array[0] =~ s/.$//g; # チェックデジット除去
# チェックデジット計算
my @num_splt = split( //, $array[0] ); # 1文字ずつ分割
my $check = 0;
for ( my $i = 0; $i < 9; $i++ ) {
$check = $check + $num_splt[$i] * ( 10 - $i );
}
$check = 11 - ( $check % 11 );
if ( $check == 10 ) {
$check = "X";
}
elsif ( $check == 11 ) {
$check = 0;
}
# タイトル
$array[1] =~ s/"//g;
$array[1] = encode_entities( $array[1] );
# 巻
$array[4] =~ s/"//g;
if ( $array[4] eq "ONLY ONE" ) {
$array[4] = "";
}
$array[4] = encode_entities( $array[4] );
# 作者
$array[5] =~ s/"//g;
$array[5] = encode_entities( $array[5] );
# 発行所
$array[12] =~ s/"//g;
$array[12] = encode_entities( $array[12] );
# 一括画像ローカル保存プラグイン利用時は備考6を画像のURLとする
if ( length( $array[37] ) > 0
&& substr( $array[37], 1, 1 ) eq ":" )
{
$array[37] = $array[66];
}
# 画像がない場合
if ( $array[37] eq "" ) {
$array[37] = $NOIMAGE;
}
print "<li class=\"img\">";
print
"<a href=\"$LINK_URL$array[0]$check/&tag=$AMAZON_TAG\"><img src=\"$array[37]\" class=\"img\" /></a>";
print
"<a href=\"$LINK_URL$array[0]$check/&tag=$AMAZON_TAG\">";
print "<div>$array[1] $array[4]</div>";
print "<div>$array[5]</div>";
print "<div>$array[12]</div>";
print "<div>ISBN:$array[0]$check</div>";
print "</a>";
print "</li>\n";
}
else {
$flg = 1;
}
$found = 0;
}
}
close(FH);
if ( $count == 0 ) {
print "<div class=\"center\">該当データなし</div></ul>\n";
}
else {
print
"</ul>\n<div class=\"center\">該当データ:${count}件</div>\n";
}
print "<form action=\"$SCRIPT_NAME\" method=\"post\">\n";
print "<input type=\"hidden\" name=\"file\" value=\"$FORM{file}\" />\n";
if ( defined( $FDATA{0} ) && $FDATA{0} ne "" ) {
print
"<input type=\"hidden\" name=\"data0\" value=\"$FDATA{0}\" />\n";
}
if ( defined( $FDATA{1} ) && $FDATA{1} ne "" ) {
print
"<input type=\"hidden\" name=\"data1\" value=\"$FDATA{1}\" />\n";
}
if ( defined( $FDATA{5} ) && $FDATA{5} ne "" ) {
print
"<input type=\"hidden\" name=\"data5\" value=\"$FDATA{5}\" />\n";
}
if ( defined( $FDATA{20} ) && $FDATA{20} ne "" ) {
print
"<input type=\"hidden\" name=\"data20\" value=\"$FDATA{20}\" />\n";
}
print "<input type=\"hidden\" name=\"ftype\" value=\"$FORM{ftype}\" />\n";
print "<input type=\"hidden\" name=\"page\" />\n";
print "</form>\n";
if ( $flg == 1 ) {
my $p = $FORM{page};
my $maxPage = int( $count / $PAGE );
if ( $count % $PAGE != 0 ) {
$maxPage++;
}
# 指定ページが範囲外の場合は先頭または最終ページにする
if ( $p < 1 ) {
$p = 1;
}
elsif ( $p > $maxPage ) {
$p = $maxPage;
}
print "<ul id=\"nextpage\"><li class=\"left\">";
if ( $p > 1 ) {
$FORM{page} = $p - 1;
print "<a href=\"javascript:send('$FORM{page}')\"><</a>";
}
print "</li><li class=\"center\">$p\/$maxPage</li>";
print "<li class=\"right\">";
if ( $p < $maxPage ) {
$FORM{page} = $p + 1;
print "<a href=\"javascript:send('$FORM{page}')\">></a>";
}
print "</li></ul>\n";
print "<ul id=\"pages\">\n";
for ( my $pagecount = 1; $pagecount <= $maxPage; $pagecount++ ) {
if ( $pagecount != $p ) {
print
"<li class=\"other\"><a href=\"javascript:send('$pagecount')\">$pagecount</a></li>\n";
}
else {
print "<li class=\"now\">$pagecount</li>\n";
}
}
print "</ul>\n";
}
}
#-----------------------------------------------------------
# ISBN変換
#-----------------------------------------------------------
sub isbn_change {
my $isbn = $_[0];
#ISBNは-を削除 x、+、*はXとみなす
$isbn =~ s/-//g;
$isbn =~ tr/+*x/X/;
return $isbn;
}
#-----------------------------------------------------------
# エラーメッセージ
#-----------------------------------------------------------
sub error {
my @msg = @_;
print "<ul id=\"result\">";
foreach my $i ( 0 .. $#msg ) {
print "$msg[$i]<br />\n";
}
print "</ul>\n";
}
因此,今后购买记录的文章应该就不会再出现了。