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