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