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        = "&nbsp;";                                                                // 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         * {&#064;XXXX} をテーブルモデルより読み取り、値をセットします。
171         * 各サブクラスで実装してください。
172         *
173         * @og.rev 3.0.0.1 (2003/02/14) 一度もValueセットしていないのに次ページ要求があった場合は、フォーマットがおかしい
174         * @og.rev 3.0.0.2 (2003/02/20) {&#064;XXXX}文字が、EXCELに表示しきれない場合に挿入されるタグの削除処理の変更。
175         * @og.rev 3.5.0.0 (2003/09/17) {&#064;XXXX}文字のスペースを、&amp;nbsp;と置き換えます。
176         * @og.rev 3.5.0.0 (2003/09/17) {&#064;XXXX}文字がアンバランス時にHybsSystemExceptionを発行する。
177         * @og.rev 3.5.5.9 (2004/06/07) {&#064;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 の ヘッダー/フッター部分は、\{\&#064;XXXX\} と、エスケープ文字が付加される
285         * ので、この文字列を見つけたら、{&#064;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         * {&#064;XXXX}文字変換後のスペースを、&amp;nbsp;と置き換えます。
327         *
328         * ただし、式などを使用すると、td タグの属性情報に{&#064;XXXX}文字が含まれ
329         * これに、EXCELのスペースである、&lt;span style="mso-spacerun:
330         * yes"&gt;&amp;nbsp;&amp;nbsp;&lt;/span&gt;
331         * と置き換えると、属性リスト中のタグという入れ子状態が発生する為、
332         * これは、置き換えません。
333         * &lt;td XXX&gt;YYY&lt;/td&gt; の YYYの範囲 を置き換えることになります。
334         *
335         * ここでは、過去の互換性を最大限確保する為に、特殊な方法で、処理します。
336         * 前後のスペースを取り除いた文字列で、かつ、2つ以上の連続したスペースが
337         * 存在する場合のみ、trim して、連続スペースを、&amp;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個以上を、&lt;span style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&lt;/span&gt;
382         * 形式に置き換えます。これは、EXCELがHTML変換する時のルールです。
383         *
384         * ここでは、スペースの個数-1 の &amp;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         * {&#064;QRCODE.XXXX} を含む 文字列の alt 属性を src 属性にセットします。
406         *
407         * QRコードの画像を入れ替えるため、alt属性に設定してある キー情報を元に、
408         * 2次元バーコード画像を作成し、そのファイル名を、src 属性に設定することで、
409         * 動的に画像ファイルのリンクを作成します。
410         * 現在のEXCELでは、バージョンによって、2種類の画像表示方法が存在するようで、
411         * 1画像に付き、2箇所の変更が必要です。この2箇所は、変換方法が異なる為、
412         * 全く別の処理を行う必要があります。
413         *
414         * &lt;v:shape ・・・ alt="{&#064;QRCODE.XXXX}" ・・・&gt;
415         *   &lt;v:imagedata src="yyy" ・・・&gt;・・・&lt;/v:shape&gt;形式とマッチし、
416         * xxx 部分と、yyy 部分を前方参照します。
417         *
418         * &lt;img ・・・ src="yyy" ・・・ alt="{&#064;QRCODE.XXXX}" ・・・ &gt; 形式とマッチし、
419         * yyy 部分と、xxx 部分を前方参照します。
420         *
421         * 画像のエンコードは、alt属性に設定した、{&#064;QRCODE.XXXX} 文字列の
422         * XXXX 部分のカラムデータ(通常、{&#064;XXXX} で取得できる値)を使用します。
423         * データが存在しない場合は、src="yyy" 部を削除することで対応します。
424         * なお、後続処理の関係で、alt="{&#064;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         *  {&#064;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}