最近开始使用私本管理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"; }
因此,今后购买记录的文章应该就不会再出现了。