001/* 002 * Copyright (c) 2009 The openGion Project. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 013 * either express or implied. See the License for the specific language 014 * governing permissions and limitations under the License. 015 */ 016package org.opengion.hayabusa.report; 017 018import org.opengion.hayabusa.common.HybsSystem; 019import org.opengion.hayabusa.common.HybsSystemException; 020import org.opengion.fukurou.system.LogWriter; 021import org.opengion.fukurou.util.QrcodeImage; 022import org.opengion.fukurou.util.ReplaceString; 023import static org.opengion.fukurou.system.HybsConst.CR ; // 6.1.0.0 (2014/12/26) 024import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE; // 6.1.0.0 (2014/12/26) refactoring 025 026import java.io.IOException; 027import java.util.concurrent.ConcurrentMap; // 6.4.3.3 (2016/03/04) 028import java.util.concurrent.ConcurrentHashMap; // 6.4.3.1 (2016/02/12) refactoring 029import java.util.regex.Pattern; 030import java.util.regex.Matcher ; 031 032/** 033 * DBTableReport インターフェース を実装したHTMLをパースするクラスです。 034 * AbstractDBTableReport を継承していますので,writeReport() のみオーバーライドして, 035 * 固定長文字ファイルの出力機能を実現しています。 036 * 037 * @og.group 帳票システム 038 * 039 * @version 4.0 040 * @author Kazuhiko Hasegawa 041 * @since JDK5.0, 042 */ 043public class DBTableReport_HTML extends AbstractDBTableReport { 044 private static final String TR_IN = "<tr" ; 045 private static final String TR_OUT = "</tr>" ; 046 private static final String TD_OUT = "</td>" ; // 3.5.5.9 (2004/06/07) 047 private static final String PAGE_BREAK = "page-break" ; 048 private static final String PAGE_END_CUT = "PAGE_END_CUT" ; // 3.6.0.0 (2004/09/17) 049 private static final String END_TAG = "</table></body></html>"; 050 private static final String CUT_TAG1 = "<span"; 051 private static final String CUT_TAG2 = "</span>"; 052 private static final String SPACE_ST = "<span style=\"mso-spacerun: yes\">"; // 3.6.0.0 (2004/09/17) 053 private static final String SPACE = " "; // 3.6.0.0 (2004/09/17) 054 private static final String SPACE_ED = " </span>"; // 3.6.0.0 (2004/09/17) 055 private static final String FRAMESET = "Excel Workbook Frameset" ; 056 057 // <td xxx="yyy">zzzz</td> 形式とマッチし、>zzzz< 部分を前方参照します。 058 private static final Pattern PTN1 = Pattern.compile("<td[^>]*(>.*?<)/td>"); 059 // >aaaa<span bb="cc">dddd</span>eeee< 形式に2文字以上のスペースを含むデータと 060 // マッチし、aaaa,dddd,eeee を前方参照します。 061 private static final Pattern PTN2 = Pattern.compile("[^>]*>([^<]*? ++[^<]*?)<"); 062 // aa bb cc 形式とマッチし、各連続スペース部分を前方参照します。 063 private static final Pattern PTN3 = Pattern.compile("( +)"); 064 065 private boolean fileEnd ; // ファイルの読み取り制御 066 067 // 3.6.1.0 (2005/01/05) QRコード(2次元バーコード)用の出力ファイル管理 068 /** 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。 */ 069 private ConcurrentMap<String,String> qrFileMap ; 070 // <v:shape ・・・ alt="{@QRCODE.XXXX}" ・・・> 071 // <v:imagedata src="yyy" ・・・>・・・</v:shape>形式とマッチし、 072 // xxx 部分と、yyy 部分を前方参照します。 073 private static final Pattern IMGPTN1 = Pattern.compile("<v:shape [^>]*alt=\"\\{@QRCODE.([^\\}]*)\\}\"[^>]*>[^<]*<v:imagedata [^>]*src=\"([^\"]*)\"[^>]*>"); 074 // <img ・・・ src="yyy" ・・・ alt="{@QRCODE.XXXX}" ・・・ > 形式とマッチし、 075 // yyy 部分と、xxx 部分を前方参照します。 076 private static final Pattern IMGPTN2 = Pattern.compile("<img [^>]*src=\"([^\"]*)\"[^>]*alt=\"\\{@QRCODE.([^\\}]*)\\}\"[^>]*>"); 077 078 // 4.0.0 (2007/06/08) pageEndCut = true 時の LINE_COPY 機能の実装 079 private static final String LINE_COPY = "LINE_COPY" ; // 4.0.0 (2007/06/08) 080 private String lineCopy ; 081 082 // 5.7.1.0 (2013/12/06) trueの場合 PAGE_END_CUTの判定にdataOver フラグを使用。falseの場合は、rowOver を使用。 083 private final boolean USE_DATAOVER = HybsSystem.sysBool( "COMPATIBLE_PAGE_END_CUT_RETRIEVAL" ); 084 085 /** 086 * デフォルトコンストラクター 087 * 088 * @og.rev 6.4.2.0 (2016/01/29) PMD refactoring. Each class should declare at least one constructor. 089 */ 090 public DBTableReport_HTML() { super(); } // これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。 091 092 /** 093 * 入力文字列 を読み取って、出力します。 094 * tr タグを目印に、1行(trタグ間)ずつ取り出します。 095 * 読み取りを終了する場合は、null を返します。 096 * 各サブクラスで実装してください。 097 * 098 * @og.rev 3.0.0.1 (2003/02/14) 一度もValueセットしていないのに次ページ要求があった場合は、フォーマットがおかしい 099 * @og.rev 3.6.0.0 (2004/09/24) フォーマットエラーの判定(formatErr)を、親クラスに移動します。 100 * 101 * @return 出力文字列 102 */ 103 @Override 104 protected String readLine() { 105 if( fileEnd ) { return null; } 106 107 // pageEndCut 時に、データがオーバーしていない間のみ、lineCopy があれば返す。 108 if( pageEndCut && !rowOver && lineCopy != null ) { 109 lineCopyCnt ++ ; // 雛形は、_0 のみが毎回返される為の、加算 110 return lineCopy ; 111 } 112 113 final StringBuilder buf ; 114 try { 115 String line = reader.readLine(); 116 if( line == null ) { 117 if( rowOver ) { 118 return null; 119 } 120 else { 121 initReader(); 122 initWriter(); 123 line = reader.readLine(); 124 if( line == null ) { return null; } 125 } 126 } 127 if( line.indexOf( FRAMESET ) >= 0 ) { 128 final String errMsg = "HTML ファイルエラー :" + line + CR 129 + "Excelファイル形式がフレームになっています。(複数シートには未対応)" ; 130 throw new HybsSystemException( errMsg ); 131 } 132 if( line.indexOf( TR_IN ) >= 0 ) { 133 buf = new StringBuilder( BUFFER_MIDDLE ); 134 buf.append( line ); 135 int trLevel = 1; // 行を表す <tr> のレベル 136 while( trLevel != 0 ) { 137 line = reader.readLine(); 138 // 4.0.0 (2005/08/31) null 参照はずし対応 139 // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..; 140 if( line == null ) { 141 final String errMsg = "HTML ファイルエラー :" + buf.toString() + CR 142 + "行(TR)の整合性が取れる前に、ファイルが終了しました。" ; 143 throw new HybsSystemException( errMsg ); 144 } 145 if( line.indexOf( TR_IN ) >= 0 ) { trLevel++ ; } 146 if( line.indexOf( TR_OUT ) >= 0 ) { trLevel-- ; } 147 buf.append( CR ).append( line ); 148 149 } 150 } 151 else { 152 return line; 153 } 154 } catch( final IOException ex ) { 155 final String errMsg = "HTML ファイル 読取時にエラーが発生しました。" + reader; 156 throw new HybsSystemException( errMsg,ex ); // 3.5.5.4 (2004/04/15) 引数の並び順変更 157 } 158 159 String rtnLine = buf.toString() ; 160 161 // lineCopy 情報の取得。 162 if( pageEndCut && !rowOver ) { 163 // LINE_COPY は削除しますので、表示上見えるようにしておいてください。 164 final int adrs = rtnLine.indexOf( LINE_COPY ); 165 if( adrs >= 0 ) { 166 lineCopy = rtnLine.substring( 0,adrs ) 167 + rtnLine.substring( adrs + LINE_COPY.length() ) ; 168 rtnLine = lineCopy ; 169 } 170 } 171 172 return rtnLine ; 173 } 174 175 /** 176 * 入力文字列 を加工して、出力します。 177 * {@XXXX} をテーブルモデルより読み取り、値をセットします。 178 * 各サブクラスで実装してください。 179 * 180 * @og.rev 3.0.0.1 (2003/02/14) 一度もValueセットしていないのに次ページ要求があった場合は、フォーマットがおかしい 181 * @og.rev 3.0.0.2 (2003/02/20) {@XXXX}文字が、EXCELに表示しきれない場合に挿入されるタグの削除処理の変更。 182 * @og.rev 3.5.0.0 (2003/09/17) {@XXXX}文字のスペースを、&nbsp;と置き換えます。 183 * @og.rev 3.5.0.0 (2003/09/17) {@XXXX}文字がアンバランス時にHybsSystemExceptionを発行する。 184 * @og.rev 3.5.5.9 (2004/06/07) {@XXXX}の連続処理のアドレス計算方法が、間違っていましたので修正します。 185 * @og.rev 3.6.0.0 (2004/09/17) pageEndCut が true の場合は、PAGE_END_CUT 文字列のある行を削除します。 186 * @og.rev 3.6.0.0 (2004/09/24) フォーマットエラーの判定(formatErr)を、親クラスに移動します。 187 * @og.rev 3.6.1.0 (2005/01/05) QRコード(2次元バーコード)の機能追加 188 * @og.rev 3.8.1.2 (2005/12/19) PAGE_END_CUTの判定にdataOver フラグを使用。 189 * @og.rev 5.7.1.0 (2013/12/06) USE_DATAOVER が trueの場合 PAGE_END_CUTの判定にdataOver フラグを使用。falseの場合は、rowOver を使用 190 * 191 * @param inLine 入力文字列 192 * 193 * @return 出力文字列 194 * @og.rtnNotNull 195 */ 196 @Override 197 protected String changeData( final String inLine ) { 198 // rowOver で、かつ ページブレークかページエンドカットの場合、処理終了。 199 if( rowOver && inLine.indexOf( PAGE_BREAK ) >= 0 ) { 200 fileEnd = true; 201 return END_TAG; 202 } 203 204 String chLine = changeHeaderFooterData( inLine ) ; 205 206 // 3.6.1.0 (2005/01/05) QRコード(2次元バーコード)の機能追加 207 if( chLine.indexOf( "{@QRCODE." ) >= 0 ) { 208 chLine = qrcodeReplace( chLine ); 209 } 210 211 int st = chLine.indexOf( "{@" ); 212 // 3.8.1.2 (2005/12/19) {@XXXX}の存在しない行も PAGE_END_CUTの判定を行う。 213 214 final StringBuilder buf = new StringBuilder( chLine ); 215 216 boolean spaceInFlag = false; // {@XXXX} 変数のデータにスペースを含むかどうかチェック 217 while( st >= 0 ) { 218 int end = buf.indexOf( "}",st+2 ); 219 220 // EXCELに表示しきれない文字は、CUT_TAG1,CUT_TAG2 が挿入されてしまう為、 221 // 削除する必要がある。 222 final int cutSt1 = buf.indexOf( CUT_TAG1,st+2 ); 223 if( cutSt1 >= 0 && cutSt1 < end ) { 224 final int cutEnd1 = buf.indexOf( ">",cutSt1 ); 225 226 final int cutSt2 = buf.indexOf( CUT_TAG2,end ); 227 if( cutSt2 >= 0 ) { 228 buf.delete( cutSt2, cutSt2 + CUT_TAG2.length() ); 229 } 230 buf.delete( cutSt1, cutEnd1+1 ); 231 // 途中をカットした為、もう一度計算しなおし。 232 end = buf.indexOf( "}",st+2 ); // 3.5.5.9 (2004/06/07) 233 } 234 235 // 3.5.5.9 (2004/06/07) 236 // 関数等を使用すると、{@XXXX} 文字列を直接加工したデータが出力される。 237 // この加工されたデータは、HTML 表示に使用されるだけのため、削除します。 238 // 削除方法は、{@XXX</td> を想定している為、 {@ から </td> の間です。 239 final int td_out = buf.indexOf( TD_OUT,st+2 ); 240 if( td_out >= 0 && td_out < end ) { 241 buf.delete( st, td_out ); 242 // {@XXXX} パラメータが消えたので、次の計算を行います。 243 st = buf.indexOf( "{@",st+4 ); // 3.5.5.9 (2004/06/07) 244 continue ; 245 } 246 247 // 途中をカットした為、もう一度計算しなおし。 248 // フォーマットがおかしい場合の処理 249 if( end < 0 ) { 250 final String errMsg = "このテンプレートファイルの {@XXXX} が、フォーマットエラーです。" 251 + CR 252 + chLine.substring( st ) ; 253 throw new HybsSystemException( errMsg ); 254 } 255 256 final String key = buf.substring( st+2,end ); 257 258 final String val = getValue( key ); 259 if( val.indexOf( " " ) >= 0 ) { spaceInFlag = true; } 260 261 // {@XXXX} を 実際の値と置き換える。 262 buf.replace( st,end+1,val ); 263 264 // {@ の 存在チェック。 265 st = buf.indexOf( "{@",st-1 ); // 3.5.5.9 (2004/06/07) 266 } 267 268 // 3.6.0.0 (2004/09/17) pageEndCut が true の場合は、PAGE_END_CUT 文字列のある行を削除します。 269 // ここで判定するのは、PAGE_END_CUT 文字そのものが、加工されている可能性があるため。 270 String rtn = buf.toString(); 271 272 final boolean flag = USE_DATAOVER ? dataOver : rowOver ; // 5.7.1.0 (2013/12/06) 273 274 if( flag && pageEndCut ) { // 5.7.1.0 (2013/12/06) 275 final String temp = rtn.replaceAll( CUT_TAG1 + "[^>]*>" ,"" ); 276 if( temp.indexOf( PAGE_END_CUT ) >= 0 ) { 277 rtn = "" ; 278 } 279 } 280 else { 281 // 3.6.0.0 (2004/09/17) スペース置き換えは、<td XXX>YYY</td> の YYYの範囲のみとする。 282 if( spaceInFlag ) { 283 rtn = spaceReplace( rtn ) ; 284 } 285 } 286 return rtn ; 287 } 288 289 /** 290 * 超特殊処理。 291 * EXCEL の ヘッダー/フッター部分は、\{\@XXXX\} と、エスケープ文字が付加される 292 * ので、この文字列を見つけたら、{@XXXX} に、戻して処理するようにする。 293 * 294 * @param inLine 入力文字列 295 * 296 * @return 出力文字列 297 * @og.rtnNotNull 298 */ 299 private String changeHeaderFooterData( final String inLine ) { 300 int st = inLine.indexOf( "\\{\\@" ); 301 if( st < 0 ) { return inLine; } 302 303 final StringBuilder buf = new StringBuilder( inLine ); 304 305 while( st >= 0 ) { 306 buf.deleteCharAt( st ); // 初めの '\' 307 buf.deleteCharAt( st+1 ); // 1文字削除している為、+1 番目を削除 308 final int end = buf.indexOf( "\\}",st+2 ); 309 // フォーマットがおかしい場合の処理 310 if( end < 0 ) { 311 final String errMsg = "このテンプレートの HeaderFooter 部分の {@XXXX} が、書式エラーです。" 312 + CR 313 + inLine ; 314 throw new HybsSystemException( errMsg ); 315 } 316 buf.deleteCharAt( end ); // 初めの '\' 317 st = buf.indexOf( "\\{\\@",end + 1 ); 318 } 319 return buf.toString(); 320 } 321 322 /** 323 * 入力文字列 を読み取って、出力します。 324 * 各サブクラスで実装してください。 325 * 326 * @param line 入力文字列 327 */ 328 @Override 329 protected void println( final String line ) { 330 writer.println( line ); 331 } 332 333 /** 334 * {@XXXX}文字変換後のスペースを、&nbsp;と置き換えます。 335 * 336 * ただし、式などを使用すると、td タグの属性情報に{@XXXX}文字が含まれ 337 * これに、EXCELのスペースである、<span style="mso-spacerun: 338 * yes">&nbsp;&nbsp;</span> 339 * と置き換えると、属性リスト中のタグという入れ子状態が発生する為、 340 * これは、置き換えません。 341 * <td XXX>YYY</td> の YYYの範囲 を置き換えることになります。 342 * 343 * ここでは、過去の互換性を最大限確保する為に、特殊な方法で、処理します。 344 * 前後のスペースを取り除いた文字列で、かつ、2つ以上の連続したスペースが 345 * 存在する場合のみ、trim して、連続スペースを、&nbsp;と置き換えます。 346 * 文字の間に連続スペースがない場合は、前後のスペースも削除せずに、 347 * 元の文字列をそのまま返します。 348 * 前後のスペースを変換してしまうと、数字型の場合に、EXCELでの計算式がエラーになります。 349 * 350 * @og.rev 3.5.0.0 (2003/09/17) 新規追加 351 * @og.rev 3.5.5.0 (2004/03/12) 連続スペースの処理をEXCELの方式に合わせる 352 * @og.rev 3.6.0.0 (2004/09/17) スペース置き換えは、<td XXX>YYY</td> の YYYの範囲のみとする。 353 * @og.rev 3.6.1.0 (2005/01/05) 置換ロジック修正(ReplaceString クラスを使用) 354 * 355 * @param target 元の文字列 356 * 357 * @return 置換えた文字列 358 */ 359 private String spaceReplace( final String target ) { 360 final ReplaceString repData = new ReplaceString(); 361 362 final Matcher match1 = PTN1.matcher( target ) ; 363 while( match1.find() ) { 364 final int st1 = match1.start(1); 365 final String grp1 = match1.group(1); 366 final Matcher match2 = PTN2.matcher( grp1 ) ; 367 while( match2.find() ) { 368 final int st2 = match2.start(1); 369 final String grp2 = match2.group(1); 370 final Matcher match3 = PTN3.matcher( grp2 ) ; 371 while( match3.find() ) { 372 373 final int st = st1 + st2 + match3.start(1); 374 final int ed = st1 + st2 + match3.end(1); 375 376 repData.add( st,ed,makeSpace( ed-st ) ); 377 } 378 } 379 } 380 381 final String rtn = repData.replaceAll( target ); 382 383 return rtn ; 384 } 385 386 /** 387 * 指定の個数のスペース文字を表す、EXCEL の記号を作成します。 388 * 389 * EXCELでは、スペース2個以上を、<span style="mso-spacerun: yes">&nbsp;&nbsp;</span> 390 * 形式に置き換えます。これは、EXCELがHTML変換する時のルールです。 391 * 392 * ここでは、スペースの個数-1 の &nbsp; を持つ、上記の文字列を作成します。 393 * 最後の一つは、本物のスペース記号を割り当てます。 394 * 395 * @og.rev 3.6.0.0 (2004/09/17) 新規追加 396 * 397 * @param cnt スペースの個数 398 * 399 * @return 置換えた文字列 400 * @og.rtnNotNull 401 */ 402 private String makeSpace( final int cnt ) { 403 final StringBuilder buf = new StringBuilder( 40 + cnt * 6 ); 404 buf.append( SPACE_ST ); 405 for( int i=1; i<cnt; i++ ) { 406 buf.append( SPACE ); 407 } 408 buf.append( SPACE_ED ); 409 410 return buf.toString(); 411 } 412 413 /** 414 * {@QRCODE.XXXX} を含む 文字列の alt 属性を src 属性にセットします。 415 * 416 * QRコードの画像を入れ替えるため、alt属性に設定してある キー情報を元に、 417 * 2次元バーコード画像を作成し、そのファイル名を、src 属性に設定することで、 418 * 動的に画像ファイルのリンクを作成します。 419 * 現在のEXCELでは、バージョンによって、2種類の画像表示方法が存在するようで、 420 * 1画像に付き、2箇所の変更が必要です。この2箇所は、変換方法が異なる為、 421 * 全く別の処理を行う必要があります。 422 * 423 * <v:shape ・・・ alt="{@QRCODE.XXXX}" ・・・> 424 * <v:imagedata src="yyy" ・・・>・・・</v:shape>形式とマッチし、 425 * xxx 部分と、yyy 部分を前方参照します。 426 * 427 * <img ・・・ src="yyy" ・・・ alt="{@QRCODE.XXXX}" ・・・ > 形式とマッチし、 428 * yyy 部分と、xxx 部分を前方参照します。 429 * 430 * 画像のエンコードは、alt属性に設定した、{@QRCODE.XXXX} 文字列の 431 * XXXX 部分のカラムデータ(通常、{@XXXX} で取得できる値)を使用します。 432 * データが存在しない場合は、src="yyy" 部を削除することで対応します。 433 * なお、後続処理の関係で、alt="{@QRCODE.XXXX}" 文字列は、削除します。 434 * 435 * @og.rev 3.6.1.0 (2005/01/05) 新規追加 436 * 437 * @param target 元の文字列 438 * 439 * @return 置換えた文字列 440 */ 441 private String qrcodeReplace( final String target ) { 442 final ReplaceString repData = new ReplaceString(); 443 444 final Matcher match1 = IMGPTN1.matcher( target ) ; 445 while( match1.find() ) { 446 final String altV = match1.group(1); 447 448 final int stAlt = match1.start(1) - 9 ; // {@QRCODE. まで遡る 449 final int edAlt = match1.end(1) + 1 ; // } を含める 450 repData.add( stAlt,edAlt,"" ); // {@QRCODE.XXXX} の部分削除 451 452 final int st = match1.start(2); 453 final int ed = match1.end(2); 454 455 final String msg = getValue( altV ); // QRコード変換する文字列の取得 456 if( msg != null && msg.length() > 0 ) { 457 final String newStr = makeQrImage( altV,msg ); // 画像ファイルのファイル名 458 repData.add( st,ed,newStr ); 459 } 460 else { 461 repData.add( st-5,ed+1,"" ); // src="yyy" 部分のみ削除 462 } 463 } 464 465 final Matcher match2 = IMGPTN2.matcher( target ) ; 466 while( match2.find() ) { 467 final int st = match2.start(1); 468 final int ed = match2.end(1); 469 470 final String altV = match2.group(2); 471 final int stAlt = match2.start(2) - 9 ; // {@QRCODE. まで遡る 472 final int edAlt = match2.end(2) + 1 ; // } を含める 473 repData.add( stAlt,edAlt,"" ); // {@QRCODE.XXXX} の部分削除 474 475 final String msg = getValue( altV ); // QRコード変換する文字列の取得 476 if( msg != null && msg.length() > 0 ) { 477 final String newStr = makeQrImage( altV,msg ); // 画像ファイルのファイル名 478 repData.add( st,ed,newStr ); 479 } 480 else { 481 repData.add( st-5,ed+1,"" ); // src="yyy" 部分のみ削除 482 } 483 } 484 485 final String rtn = repData.replaceAll( target ) ; 486 487 return rtn ; 488 } 489 490 /** 491 * 指定のカラム名と、QRコード変換する文字列より、画像を作成します。 492 * 493 * 返り値は、作成した画像ファイルのファイル名です。 494 * これは、データが存在しない場合に、src="" を返す必要があるため、 495 * (でないと、画像へのリンクが表示されてしまう。) 496 * src="./帳票ID.files/image00x.png" という画像ファイルのアドレス部分を 497 * {@QRCODE_カラム名} 形式に変更しておく必要があります。 498 * 499 * @og.rev 3.6.1.0 (2005/01/05) 新規追加 500 * @og.rev 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。 501 * @og.rev 6.4.3.2 (2016/02/19) ConcurrentHashMap のkey,valともに、NOT NULL制限あり。 502 * 503 * @param key カラム名 504 * @param msg QRコード変換する文字列 505 * 506 * @return 画像ファイルのファイル名 507 */ 508 private String makeQrImage( final String key, final String msg ) { 509 if( key == null || msg == null || msg.isEmpty() ) { return "" ; } 510 511 // 6.4.3.2 (2016/02/19) ConcurrentHashMap のkey,valともに、NOT NULL制限あり。 512 String realClmName = key ; 513 final int sp = key.lastIndexOf( '_' ); 514 if( sp >= 0 ) { 515 try { 516 final int row = Integer.parseInt( key.substring( sp+1 ) ); 517 final int realRow = getRealRow( row ); 518 realClmName = key.substring( 0,sp ) + "_" + realRow ; 519 } 520 catch( final NumberFormatException e) { // 4.0.0 (2005/01/31) 521 final String errMsg = "警告:QRCODE名のヘッダーに'_'カラム名が使用"; 522 LogWriter.log( errMsg ); 523 } 524 } 525 526 // 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。 527 if( qrFileMap == null ) { qrFileMap = new ConcurrentHashMap<>(); } 528 if( qrFileMap.containsKey( realClmName ) ) { // Map にすでに存在している。 529 return qrFileMap.get( realClmName ); 530 } 531 532 // 帳票ID を元に、画像ファイルの保存フォルダを求めます。 533 final String filename = "./" + listId + ".files/" + realClmName + ".png"; 534 final String fullAddress = htmlDir + filename ; 535 536 final QrcodeImage qrImage = new QrcodeImage(); 537 qrImage.init( msg,fullAddress ); 538 qrImage.saveImage(); 539 540 qrFileMap.put( realClmName,filename ); 541 return filename; 542 } 543}