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.fukurou.util;
017
018import java.util.Locale;
019import java.util.Set;
020import java.util.HashSet;
021import java.util.Arrays;
022
023import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE;
024
025/**
026 * TagBuffer.java は、共通的に使用される 簡易タグ作成クラスです。
027 *
028 * ここでは、4つの形式(TAG , CSS , JSON , ARRAY )をサポートします。
029 *   TAG形式:
030 *     BODYなし  <tag key1="val1" key2="val2" ・・・/>
031 *     BODYあり  <tag key1="val1" key2="val2" ・・・>body</tag>
032 *
033 *   CSS形式:
034 *     { key1:val1; key2:val2; ・・・ }
035 *
036 *   JSON形式:
037 *     文字  { "key1":"val1", "key2":"val2", ・・・ }
038 *     数値  { "key1":num1  , "key2":num2  , ・・・ }
039 *
040 *   ARRAY形式:
041 *     文字  [ "val1", "val2", ・・・ ]
042 *     数値  [  num1 ,  num2 , ・・・ ]
043 *
044 * これらの形式は、#startTag( String ) , #startCss( String ) , #startJson() , #startArray() 
045 * メソッドで始まり、#make() で、完了します。
046 * 完了とは、内部の StringBuilder に書き込まれることを意味しますので、再び、
047 * startXXX()メソッドから始めることが可能です。
048 * つまり、一つのインスタンスに、TAGやCSSをまとめて追記していくことが出来ます。
049 * 最後に、#toString() メソッドで、内部の StringBuilder を文字列として返します。
050 *
051 * TagBuffer クラスの中身を全面的に見直しています。また、過去のTagBufferと互換性を取るための
052 * メソッドも残していますが、順次、削除していく予定です。
053 *
054 * もっと高度な機能が必要であれば、{@link org.opengion.fukurou.util.Attributes } をご参照ください。
055 *
056 * @og.rev 7.0.1.0 (2018/10/15) 新規作成
057 * @og.group ユーティリティ
058 *
059 * @version  7.0
060 * @author       Kazuhiko Hasegawa
061 * @since    JDK8.0,
062 */
063public final class TagBuffer {
064        private static enum DTYPE {             // 処理中のデータ形式
065                /* TAG形式 */     TAG ,
066                /* CSS形式 */     CSS ,
067                /* JSON形式 */    JSON ,
068                /* ARRAY形式 */   ARY ;
069        }
070
071        // 空要素となるタグ名
072        private static final Set<String> YOSO_SET = new HashSet<>( Arrays.asList( "area","base","br","col","command","embed","hr","img","input"
073                                                                                                                                                                ,"keygen","link","meta","param","source","track","wbr" ) );
074
075        private final   StringBuilder tagBuf  = new StringBuilder( BUFFER_MIDDLE );             // タグ構築用のバッファ(makeでクリア)
076        private final   StringBuilder bodyBuf = new StringBuilder( BUFFER_MIDDLE );             // タグ構築用のバッファ(makeでクリア)
077        private final   StringBuilder makeBuf = new StringBuilder( BUFFER_MIDDLE );             // make実行時のバッファ(makeでappend)
078
079        private         DTYPE   dType   = DTYPE.TAG     ;       // データ形式(初期値は、タグ)
080        private         String  name    ;                               // タグ名か、セレクター
081        private         char    kvsep   ;                               // key-val 分離文字( '=' か、':' )
082        private         String  endStr  ;                               // 終端連結時文字( " " か、"; " か、", " など)
083
084        private         String cacheTag ;                               // TagBufferと互換性を取るための、makeTag 実行時のキャッシュ
085        private         boolean isKaraTag ;                             // 空要素のタグ名かどうかの識別フラグです。
086
087        /**
088         * デフォルトコンストラクター
089         *
090         */
091        public TagBuffer() { super(); }                 // これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。
092
093        /**
094         * タグ名を指定した、タグ構築用のコンストラクター
095         *
096         * これは、TagBuffer クラスとの互換性の為に用意されたコンストラクターです。
097         *
098         * @param       name    タグの名前
099         */
100        public TagBuffer( final String name ) {
101                startTag( name );
102        }
103
104        /**
105         * TAG形式のデータ作成を宣言します。
106         *
107         *   TAG形式:
108         *     BODYなし  &lt;tag key1="val1" key2="val2" ・・・/&gt;
109         *     BODYあり  &lt;tag key1="val1" key2="val2" ・・・&gt;body&lt;/tag&gt;
110         *
111         * @param       name    タグの名前
112         * @return      自分自身
113         * @og.rtnNotNull
114         */
115        public TagBuffer startTag( final String name ) {
116                return start( DTYPE.TAG , name , '='," " );
117        }
118
119        /**
120         * CSS形式のデータ作成を宣言します。
121         *
122         *   CSS形式:
123         *     { key1:val1; key2:val2; ・・・ }
124         *
125         * @param       name    CSSのセレクター
126         * @return      自分自身
127         * @og.rtnNotNull
128         */
129        public TagBuffer startCss( final String name ) {
130                return start( DTYPE.CSS , name , ':' , "; " );
131        }
132
133        /**
134         * JSON形式のデータ作成を宣言します。
135         *
136         *   JSON形式:
137         *     文字  { "key1":"val1", "key2":"val2", ・・・ }
138         *     数値  { "key1":num1  , "key2":num2  , ・・・ }
139         *
140         * @return      自分自身
141         * @og.rtnNotNull
142         */
143        public TagBuffer startJson() {
144                return start( DTYPE.JSON , null , ':' , ", " );         //name は使いません。
145        }
146
147        /**
148         * ARRAY形式のデータ作成を宣言します。
149         *
150         *   ARRAY形式:
151         *     文字  [ "val1", "val2", ・・・ ]
152         *     数値  [  num1 ,  num2 , ・・・ ]
153         *
154         * @return      自分自身
155         * @og.rtnNotNull
156         */
157        public TagBuffer startArray() {
158                return start( DTYPE.ARY , null , ' ' , ", " );          // name , kvsep は使いません。
159        }
160
161        /**
162         * 指定のデータ形式の開始を宣言します。
163         *
164         * このメソッドは、#startTag( String ) , #startCss( String ) , #startJson() , #startArray() から
165         * 呼ばれる集約メソッドです。
166         * 各処理に必要な情報は、呼び出し元で設定しています。
167         *
168         * @param       type     タグの種類を示す enum DTYPE { TAG , CSS , JSON , ARY ; }
169         * @param       name     タグの名前か、CSSのセレクター
170         * @param       kvsep    key-val 分離文字( '=' か、':' )
171         * @param       endStr   終端連結時文字( " " か、"; " か、", " など)
172         * @return      自分自身
173         * @og.rtnNotNull
174         */
175        private TagBuffer start( final DTYPE type , final String name , final char kvsep , final String endStr ) {
176                this.dType      = type;
177                this.name       = name;
178                this.kvsep      = kvsep;
179                this.endStr     = endStr;
180
181                // 空要素かどうかを判定します。
182                isKaraTag       = name != null && YOSO_SET.contains( name.toLowerCase( Locale.JAPAN ) );
183
184                return this;
185        }
186
187        /**
188         * データ本体のバッファに直接値を追加します。
189         *
190         * 指定の可変長文字列は、何の加工も無く、' '(スペース)で区切られて、直接追加されます。
191         * データ形式以外の文字列を前後、間に追記したい場合に、都度呼びます。
192         * 
193         * 引数が null や、空文字列の場合は、何もしません。
194         *
195         * @param       vals    データ本体のバッファに直接追加する可変長文字列
196         * @return      自分自身
197         * @see         #addBuffer( String , boolean )
198         * @og.rtnNotNull
199         */
200        public TagBuffer addBuffer( final String... vals ) {
201                if( vals != null ) {
202                        for( final String val : vals ) {
203                                if( StringUtil.isNotNull( val ) ) {
204                                        makeBuf.append( val ).append( ' ' );
205                                }
206                        }
207                }
208
209                return this ;
210        }
211
212        /**
213         * データ本体のバッファに直接値を追加します。
214         *
215         * 指定の文字列は、何の加工も無く、' '(スペース)で区切られて、直接追加されます。
216         * データ形式以外の文字列を前後、間に追記したい場合に、都度呼びます。
217         * 
218         * 引数が null や、空文字列の場合は、何もしません。
219         *
220         * @param       val     データ本体のバッファに直接追加する文字列
221         * @param       isUse   追加処理を行うかどうかの判定(true:処理する/false:処理しない)
222         * @return      自分自身
223         * @see         #addBuffer( String... )
224         * @og.rtnNotNull
225         */
226        public TagBuffer addBuffer( final String val , final boolean isUse ) {
227                return isUse ? addBuffer( val ) : this ;
228        }
229
230//      /**
231//       * データ本体のバッファに直接値を追加します。
232//       *
233//       * 指定の可変長文字列は、何の加工も無く、' '(スペース)で区切られて、直接追加されます。
234//       * データ形式以外の文字列を前後、間に追記したい場合に、都度呼びます。
235//       * 
236//       * 引数が null や、空文字列の場合は、何もしません。
237//       *
238//       * @param       vals    データ本体のバッファに直接追加する文字列配列
239//       * @param       isUse   追加処理を行うかどうかの判定(true:処理する/false:処理しない)
240//       * @return      自分自身
241//       * @see         #addBuffer( String... )
242//       * @og.rtnNotNull
243//       */
244//      public TagBuffer addBuffer( final String[] vals , final boolean isUse ) {
245//              if( isUse && vals != null ) {
246//                      for( final String val : vals ) {
247//                              if( StringUtil.isNotNull( val ) ) {
248//                                      makeBuf.append( val ).append( ' ' );
249//                              }
250//                      }
251//              }
252//
253//              return this ;
254//      }
255
256        /**
257         * データの登録エリアのバッファに直接値を追加します。
258         *
259         * これは、TagBuffer クラスとの互換性の為に用意されたメソッドです。
260         * 近い将来、削除します。
261         *
262         * @param       val     データの登録エリアのバッファに直接追加する文字列
263         * @return      自分自身
264         * @see         #addBody( String... )
265         * @og.rtnNotNull
266         */
267        public TagBuffer add( final String val ) {
268                return addOptions( val );
269        }
270
271        /**
272         * データの登録エリアのバッファに直接値を追加します。
273         *
274         * 指定の可変長文字列は、何の加工も無く、' '(スペース)で区切られて、直接追加されます。
275         * これは、データ生成中のバッファに対して、追加を行います。
276         * 例えば、外部で、タグやCSSのデータ形式を作成済みで、その文字列を追記するなどです。
277         * 
278         * 引数が null や、空文字列の場合は、何もしません。
279         *
280         * @param       vals    データの登録エリアのバッファに直接追加する可変長文字列
281         * @return      自分自身
282         * @see         #addOptions( String... )
283         * @og.rtnNotNull
284         */
285        public TagBuffer addOptions( final String... vals ) {
286                if( vals != null ) {
287                        for( final String val : vals ) {
288                                if( StringUtil.isNotNull( val ) ) {
289                                        tagBuf.append( val ).append( ' ' );
290                                }
291                        }
292                }
293
294                return this ;
295        }
296
297        /**
298         * データの登録エリアのバッファに直接値を追加します。
299         *
300         * 指定の文字列は、何の加工も無く、' '(スペース)で区切られて、直接追加されます。
301         * これは、データ生成中のバッファに対して、追加を行います。
302         * 例えば、外部で、タグやCSSのデータ形式を作成済みで、その文字列を追記するなどです。
303         * 
304         * 引数が null や、空文字列の場合は、何もしません。
305         *
306         * @param       val     データの登録エリアのバッファに直接追加する文字列
307         * @param       isUse   追加処理を行うかどうかの判定(true:処理する/false:処理しない)
308         * @return      自分自身
309         * @see         #addOptions( String... )
310         * @og.rtnNotNull
311         */
312        public TagBuffer addOptions( final String val , final boolean isUse ) {
313                return isUse ? addOptions( val ) : this ;
314        }
315
316//      /**
317//       * データの登録エリアのバッファに直接値を追加します。
318//       *
319//       * 指定の可変長文字列は、何の加工も無く、' '(スペース)で区切られて、直接追加されます。
320//       * これは、データ生成中のバッファに対して、追加を行います。
321//       * 例えば、外部で、タグやCSSのデータ形式を作成済みで、その文字列を追記するなどです。
322//       * 
323//       * 引数が null や、空文字列の場合は、何もしません。
324//       *
325//       * @param       vals    データの登録エリアのバッファに直接追加する文字列配列
326//       * @param       isUse   追加処理を行うかどうかの判定(true:処理する/false:処理しない)
327//       * @return      自分自身
328//       * @see         #addOptions( String... )
329//       * @og.rtnNotNull
330//       */
331//      public TagBuffer addOptions( final String[] vals , final boolean isUse ) {
332//              if( isUse && vals != null ) {
333//                      for( final String val : vals ) {
334//                              if( StringUtil.isNotNull( val ) ) {
335//                                      tagBuf.append( val ).append( ' ' );
336//                              }
337//                      }
338//              }
339//
340//              return this ;
341//      }
342
343        /**
344         * データの BODY部のバッファに直接値を追加します(データ形式 TAGのみ有効)。
345         *
346         * 指定の可変長文字列は、何の加工も無く、' '(スペース)で区切られて、直接追加されます。
347         * これは、データの BODY部のバッファに対して、追加を行います。
348         * BODY部は、タグのbody として使用されているデータで、BODY有り無しの判定は、
349         * このデータが登録されたかどうかで判定しています。
350         * 
351         * 引数が null の場合は、何もしません。
352         *
353         * @param       vals    データのBODY部のバッファに直接追加する可変長文字列
354         * @return      自分自身
355         * @see         #addOptions( String... )
356         * @og.rtnNotNull
357         */
358        public TagBuffer addBody( final String... vals ) {
359                if( dType == DTYPE.TAG && vals != null ) {
360                        bodyBuf.append( String.join( " ",vals ) );
361
362        //              for( final String val : vals ) {
363        //                      if( val != null ) {
364        //                              bodyBuf.append( val ).append( ' ' );
365        //                      }
366        //              }
367                }
368
369                return this ;
370        }
371
372        /**
373         * データの BODY部のバッファに直接値を追加します(データ形式 TAGのみ有効)。
374         *
375         * 指定の文字列は、何の加工も無く、' '(スペース)で区切られて、直接追加されます。
376         * これは、データの BODY部のバッファに対して、追加を行います。
377         * BODY部は、タグのbody として使用されているデータで、BODY有り無しの判定は、
378         * このデータが登録されたかどうかで判定しています。
379         * 
380         * 引数が null や、空文字列の場合は、何もしません。
381         *
382         * @param       val     データのBODY部のバッファに直接追加する文字列
383         * @param       isUse   追加処理を行うかどうかの判定(true:処理する/false:処理しない)
384         * @return      自分自身
385         * @see         #addOptions( String... )
386         * @og.rtnNotNull
387         */
388        public TagBuffer addBody( final String val , final boolean isUse ) {
389                return isUse ? addBody( val ) : this ;
390        }
391
392//      /**
393//       * データの BODY部のバッファに直接値を追加します(データ形式 TAGのみ有効)。
394//       *
395//       * 指定の可変長文字列は、何の加工も無く、' '(スペース)で区切られて、直接追加されます。
396//       * これは、データの BODY部のバッファに対して、追加を行います。
397//       * BODY部は、タグのbody として使用されているデータで、BODY有り無しの判定は、
398//       * このデータが登録されたかどうかで判定しています。
399//       * 
400//       * 引数が null や、空文字列の場合は、何もしません。
401//       *
402//       * @param       vals    データのBODY部のバッファに直接追加する文字列配列
403//       * @param       isUse   追加処理を行うかどうかの判定(true:処理する/false:処理しない)
404//       * @return      自分自身
405//       * @see         #addOptions( String... )
406//       * @og.rtnNotNull
407//       */
408//      public TagBuffer addBody( final String[] vals , final boolean isUse ) {
409//              if( isUse && dType == DTYPE.TAG && vals != null ) {
410//                      for( final String val : vals ) {
411//                              if( StringUtil.isNotNull( val ) ) {
412//                                      bodyBuf.append( val ).append( ' ' );
413//                              }
414//                      }
415//              }
416//
417//              return this ;
418//      }
419
420        /**
421         * データの登録エリアに、数値型として値を追加します(データ形式 ARRAYのみ有効)。
422         *
423         * データが、数値型の場合、ダブルコーテーションは付けません。
424         * 指定の可変長文字列は、何の加工も無く、endStr(終端連結時文字)で区切られて、追加されます。
425         * ARRAY形式のみ有効なので、endStrは、',' になります。
426         * データが、null の場合、endStr(終端連結時文字)のみが、追加されますので、
427         * データの個数は、変わりません。
428         * 最後に必ず、endStr(終端連結時文字)が付きます。
429         *
430         * @param       vals    データの登録エリアに、数値型として追加する可変長文字列
431         * @return      自分自身
432         * @see         #addNum( String,boolean )
433         * @og.rtnNotNull
434         */
435        public TagBuffer addNum( final String... vals ) {
436                if( dType == DTYPE.ARY && vals != null ) {
437                        for( final String val : vals ) {
438                                if( val == null ) {
439                                        tagBuf.append( endStr );
440                                }
441                                else {
442                                        tagBuf.append( val ).append( endStr );
443                                }
444                        }
445                }
446
447                return this ;
448        }
449
450        /**
451         * データの登録エリアに、数値型として値を追加します(データ形式 ARRAYのみ有効)。
452         *
453         * データが、数値型の場合、ダブルコーテーションは付けません。
454         * 指定の文字列は、何の加工も無く、endStr(終端連結時文字)で区切られて、追加されます。
455         * ARRAY形式のみ有効なので、endStrは、',' になります。
456         * データが、null の場合、endStr(終端連結時文字)のみが、追加されます。
457         * 最後に必ず、endStr(終端連結時文字)が付きます。
458         *
459         * @param       val     データの登録エリアに、数値型として追加する文字列
460         * @param       isUse   追加処理を行うかどうかの判定(true:処理する/false:処理しない)
461         * @return      自分自身
462         * @see         #addNum( String... )
463         * @og.rtnNotNull
464         */
465        public TagBuffer addNum( final String val , final boolean isUse ) {
466                return isUse ? addNum( val ) : this ;
467        }
468
469//      /**
470//       * データの登録エリアに、数値型として値を追加します(データ形式 ARRAYのみ有効)。
471//       *
472//       * データが、数値型の場合、ダブルコーテーションは付けません。
473//       * 指定の可変長文字列は、何の加工も無く、endStr(終端連結時文字)で区切られて、追加されます。
474//       * ARRAY形式のみ有効なので、endStrは、',' になります。
475//       * データが、null の場合、endStr(終端連結時文字)のみが、追加されますので、
476//       * データの個数は、変わりません。
477//       * 最後に必ず、endStr(終端連結時文字)が付きます。
478//       *
479//       * @param       vals    データの登録エリアに、数値型として追加する文字列配列
480//       * @param       isUse   追加処理を行うかどうかの判定(true:処理する/false:処理しない)
481//       * @return      自分自身
482//       * @see         #addNum( String... )
483//       * @og.rtnNotNull
484//       */
485//      public TagBuffer addNum( final String[] vals , final boolean isUse ) {
486//              if( isUse && dType == DTYPE.ARY && vals != null ) {
487//                      for( final String val : vals ) {
488//                              if( val == null ) {
489//                                      tagBuf.append( endStr );
490//                              }
491//                              else {
492//                                      tagBuf.append( val ).append( endStr );
493//                              }
494//                      }
495//              }
496//
497//              return this ;
498//      }
499
500        /**
501         * データの登録エリアに、文字型として値を追加します(データ形式 ARRAYのみ有効)。
502         *
503         * データが、文字型の場合、ダブルコーテーションを付けて登録します。
504         * 指定の可変長文字列は、何の加工も無く、endStr(終端連結時文字)で区切られて、追加されます。
505         * ARRAY形式のみ有効なので、endStrは、',' になります。
506         * データが、null の場合、""(空文字列)と、endStr(終端連結時文字)が、追加されますので、
507         * データの個数は、変わりません。
508         * 最後に必ず、endStr(終端連結時文字)が付きます。
509         *
510         * @param       vals    データの登録エリアに、文字型として追加する可変長文字列
511         * @return      自分自身
512         * @see         #addStr( String,boolean )
513         * @og.rtnNotNull
514         */
515        public TagBuffer addStr( final String... vals ) {
516                if( dType == DTYPE.ARY && vals != null ) {
517                        for( final String val : vals ) {
518                                if( val == null ) {
519                                        tagBuf.append( "\"\"" ).append( endStr );
520                                }
521                                else {
522                                        tagBuf.append( '"' ).append( val ).append( '"' ).append( endStr );
523                                }
524                        }
525                }
526
527                return this ;
528        }
529
530        /**
531         * データの登録エリアに、文字型として値を追加します(データ形式 ARRAYのみ有効)。
532         *
533         * データが、文字型の場合、ダブルコーテーションを付けて登録します。
534         * 指定の文字列は、何の加工も無く、endStr(終端連結時文字)で区切られて、追加されます。
535         * ARRAY形式のみ有効なので、endStrは、',' になります。
536         * データが、null の場合、""(空文字列)と、endStr(終端連結時文字)が、追加されます。
537         * 最後に必ず、endStr(終端連結時文字)が付きます。
538         *
539         * @param       val     データの登録エリアに、文字型として追加する文字列
540         * @param       isUse   追加処理を行うかどうかの判定(true:処理する/false:処理しない)
541         * @return      自分自身
542         * @see         #addStr( String... )
543         * @og.rtnNotNull
544         */
545        public TagBuffer addStr( final String val , final boolean isUse ) {
546                return isUse ? addStr( val ) : this ;
547        }
548
549//      /**
550//       * データの登録エリアに、文字型として値を追加します(データ形式 ARRAYのみ有効)。
551//       *
552//       * データが、文字型の場合、ダブルコーテーションを付けて登録します。
553//       * 指定の可変長文字列は、何の加工も無く、endStr(終端連結時文字)で区切られて、追加されます。
554//       * ARRAY形式のみ有効なので、endStrは、',' になります。
555//       * データが、null の場合、""(空文字列)と、endStr(終端連結時文字)が、追加されますので、
556//       * データの個数は、変わりません。
557//       * 最後に必ず、endStr(終端連結時文字)が付きます。
558//       *
559//       * @param       vals    データの登録エリアに、文字型として追加する文字列配列
560//       * @param       isUse   追加処理を行うかどうかの判定(true:処理する/false:処理しない)
561//       * @return      自分自身
562//       * @see         #addStr( String... )
563//       * @og.rtnNotNull
564//       */
565//      public TagBuffer addStr( final String[] vals , final boolean isUse ) {
566//              if( isUse && dType == DTYPE.ARY && vals != null ) {
567//                      for( final String val : vals ) {
568//                              if( val == null ) {
569//                                      tagBuf.append( "\"\"" ).append( endStr );
570//                              }
571//                              else {
572//                                      tagBuf.append( '"' ).append( val ).append( '"' ).append( endStr );
573//                              }
574//                      }
575//              }
576//
577//              return this ;
578//      }
579
580        /**
581         * キーと値のセットを、追加します。
582         *
583         * key が nullか、ゼロ文字列の場合は、なにもしません。
584         * val が null の場合は、なにもしません。
585         * val が、ゼロ文字列の場合は、追加します
586         *
587         * val に、ダブルコーテーション(")が含まれている場合は、属性値をシングルコーテーション
588         * でくくります。
589         * 両方含まれている場合は、シングルコーテーションをエスケープ文字(&amp;#39;)に変換します。
590         *
591         * @param       key      属性キー(nullか、ゼロ文字列の場合は、なにもしません)
592         * @param       val      属性値 (null の場合は、なにもしない)
593         *
594         * @return      自分自身
595         * @see         #add( String , String , boolean )
596         * @og.rtnNotNull
597         */
598        public TagBuffer add( final String key,final String val ) {
599                return add( key,val,true );
600        }
601
602        /**
603         * キー配列と値配列のセットを、追加します。
604         *
605         * これは、配列の数だけ、#add( boolean , String , String ) を呼び出して処理を行います。
606         * 個々の配列内のkey,valは、先のメソッドの規約に従います。
607         * キー配列と値配列のサイズが異なる場合や、配列が null の場合は、何もしません。
608         * (エラーではなく何もしないのでご注意ください。)
609         *
610         * @param       keys     属性キー配列
611         * @param       vals     属性値配列
612         *
613         * @return      自分自身
614         * @see         #add( String , String , boolean )
615         * @og.rtnNotNull
616         */
617        public TagBuffer add( final String[] keys,final String[] vals ) {
618                if( keys != null && vals != null && keys.length == vals.length ) {
619                        for( int i=0; i<keys.length; i++ ) {
620                                add( keys[i] , vals[i] , true );
621                        }
622                }
623
624                return this ;
625        }
626
627        /**
628         * キーと値のセットを、追加します(データ形式 ARRAY以外有効)。
629         *
630         * key が nullか、ゼロ文字列の場合は、なにもしません。
631         * val が null の場合は、なにもしません。
632         * val が、ゼロ文字列の場合は、追加します
633         * isUse が、false の場合は、なにもしません。
634         *
635         * val に、ダブルコーテーション(")が含まれている場合は、属性値をシングルコーテーション
636         * でくくります。
637         * 両方含まれている場合は、シングルコーテーションをエスケープ文字(&amp;#39;)に変換します。
638         * 
639         * isUse に、false を指定すると、属性は追加しません。これは、if( isUse ) { tagBuf.add( key,val ); }
640         * の様な判定処理を、tagBuf.add( key,val,isUse ); とすることで、StringBuilderの様に、連結して使用できます。
641         *
642         * 値 に、ダブルコーテーション(")が含まれている場合は、属性値をシングルコーテーション
643         * でくくります。
644         * 両方含まれている場合は、シングルコーテーションをエスケープ文字に変換します。
645         *
646         * @param       key             属性キー(nullか、ゼロ文字列の場合は、なにもしません)
647         * @param       val             属性値 (null の場合は、なにもしない)
648         * @param       isUse   属性を追加かどうかを決めるフラグ(true:追加する/false:何もしない)
649         *
650         * @return      自分自身
651         * @see         #add( String , String )
652         * @og.rtnNotNull
653         */
654        public TagBuffer add( final String key , final String val , final boolean isUse ) {
655                if( isUse && dType != DTYPE.ARY && StringUtil.isNotNull( key ) && val != null ) {
656                        if( dType == DTYPE.JSON ) {                                     // JSON の場合は、keyにダブルクオートを付けます。
657                                tagBuf.append( '"' ).append( key ).append( '"' ).append( kvsep );
658                        }
659                        else {
660                                tagBuf.append( key ).append( kvsep );   // TAG,CSS
661                        }
662
663                        if( dType == DTYPE.CSS ) {                                      // CSS の場合は、属性にダブルクオートは付けません。
664                                tagBuf.append( val );
665                        }
666                        else {
667                                // ダブルコーテーションがある場合は、シングルコーテーションで囲う。
668                                if( val.indexOf( '"' ) >= 0 ) {
669                                        // ただし、シングルコーテーションを含む場合は、エスケープ文字に置き換える。
670                                        tagBuf.append( '\'' )
671                                                .append( val.indexOf( '\'' ) >= 0 ? val.replaceAll( "'","&#39;" ) : val )
672                                                .append( '\'' );
673                                }
674                                else {
675                                        tagBuf.append( '"' ).append( val ).append( '"' );
676                                }
677                        }
678                        tagBuf.append( endStr );
679                }
680
681                return this ;
682        }
683
684        /**
685         * 整形されたデータ文字列を 作成します。
686         * このメソッドは、startXXX で開始さえたデータ形式に応じて、データ本体のバッファに追記します。
687         * 一連のデータ作成処理は、この、#make() メソッドで終了します。
688         * 
689         * その後、startXXX を呼び出すことで、新しいデータ形式の作成を開始できます。
690         *
691         * @return      自分自身
692         * @og.rtnNotNull
693         */
694        public TagBuffer make() {
695                if( dType == DTYPE.TAG ) {                                              // TAG の場合
696                        if( name == null ) {
697                                makeBuf.append( tagBuf );
698                        }
699                        else {
700                                makeBuf.append( '<' ).append( name ).append( ' ' ).append( tagBuf );
701
702                                if( isKaraTag ) {                                               // 空要素の場合
703        //                      if( bodyBuf.length() == 0 ) {                   // BODY がない
704        //                              makeBuf.append( "/>" );
705                                        makeBuf.append( '>' );                          // XHTML 以外の文法
706                                }
707                                else {
708                                        makeBuf.append( '>' )
709                                                .append( bodyBuf )
710                                                .append( "</" ).append( name ).append( '>' );
711                                }
712                        }
713                }
714                else if( dType == DTYPE.CSS ) {                                 // CSS の場合
715                        if( name == null ) {
716                                makeBuf.append( tagBuf );
717                        }
718                        else {
719                                makeBuf.append( name ).append( " { " ).append( tagBuf ).append( " } " );
720                        }
721                }
722                else if( dType == DTYPE.JSON ) {                                // JSON の場合
723                        makeBuf.append( " { " ).append( tagBuf ).append( " }, " );
724                }
725                else if( dType == DTYPE.ARY ) {                                 // ARY の場合
726                        makeBuf.append( " [ " ).append( tagBuf ).append( " ], " );
727                }
728
729                tagBuf.setLength( 0 );          // StringBuilder のクリア
730                bodyBuf.setLength( 0 );         // StringBuilder のクリア
731
732                return this ;
733        }
734
735        /**
736         * TAG形式のデータの、要素部分(BODYの直前まで)の文字列を作成して返します。
737         *
738         * これは、TAG形式で、BODY部分が、別途処理が必要な場合、要素部分まで作成したい場合に使用します。
739         * また、TAG形式以外の場合は、データの登録エリアのバッファをそのまま文字列にして返します。
740         *
741         * この処理を実行すると、内部のすべてのバッファがクリアされます。
742         * 唯一、nameや、区切り文字情報は残っていますので、続きから、BODY の登録や、次のタグの登録が可能です。
743         * 最後は、#toAfter() メソッドを実行すれば、タグとしての形式を保つことが可能です。
744         *
745         * @return      要素部分(BODYの直前まで)の文字列
746         * @see         #toAfter()
747         * @og.rtnNotNull
748         */
749        public String toBefore() {
750                if( dType == DTYPE.TAG && name != null ) {
751                        makeBuf.append( '<' ).append( name ).append( ' ' ).append( tagBuf ).append( '>' );
752                }
753                else {
754                        makeBuf.append( tagBuf );
755                }
756
757                final String rtn = makeBuf.toString();
758
759                clear();                                        // すべての内部情報をクリアします。
760
761                return rtn;
762        }
763
764        /**
765         * TAG形式のデータの、BODY+終了タグの文字列を作成して返します。
766         *
767         * 通常は、#toBefore() を呼んだ後に、最後に実行するメソッドです。
768         * この処理を実行すると、内部のすべてのバッファがクリアされます。
769         *
770         * @return      要素部分(BODYの直前まで)の文字列
771         * @see         #toBefore()
772         * @og.rtnNotNull
773         */
774        public String toAfter() {
775                if( dType == DTYPE.TAG && name != null ) {
776                                makeBuf.append( bodyBuf ).append( "</" ).append( name ).append( '>' );
777                }
778                else {
779                        makeBuf.append( bodyBuf );
780                }
781
782                final String rtn = makeBuf.toString();
783
784                clear();                                        // すべての内部情報をクリアします。
785
786                return rtn;
787        }
788
789        /**
790         * 内部データのバッファをすべてクリアします。
791         *
792         * 内部データには、データ登録バッファ、データBODY部バッファ、データ本体バッファと
793         * 3種類のバッファを持っています。
794         *
795         * この、3種類のバッファすべてをクリアします。
796         *
797         * @return      自分自身
798         * @og.rtnNotNull
799         */
800        public TagBuffer clear() {
801                tagBuf.setLength( 0 );          // StringBuilder のクリア
802                bodyBuf.setLength( 0 );         // StringBuilder のクリア
803                makeBuf.setLength( 0 );         // StringBuilder のクリア
804
805                return this ;
806        }
807
808        /**
809         * 内部データの文字列を返します。
810         *
811         * データ本体のバッファを文字列に変換して返します。
812         * これは、#make() か、#tagBefore() を実行後に行います。そうしないと、
813         * データが設定されていません。
814         * この処理を行っても、データはクリアされないため、再び処理を行うことが可能です。
815         * 中間のデータを確認する場合や、最後にデータを取り出す場合に利用します。
816         *
817         * @return      データ本体の文字列
818         */
819        @Override
820        public String toString() {
821                return makeBuf.toString();
822        }
823
824        /**
825         * 内部データの文字列を返します。
826         *
827         * return tagBuf.make().toString(); と同じ結果を返します。
828         *
829         * これは、TagBuffer クラスとの互換性の為に用意されたメソッドです。
830         * 内部にキャッシュとして持つため、最初に一度実行すると、以降、同じ値しか返されません。
831         *
832         * @return      データ本体の文字列
833         */
834        public String makeTag() {
835                if( cacheTag == null ) {
836                        cacheTag = make().toString();
837                }
838
839                return cacheTag;
840        }
841
842        /**
843         * 行番号付きのタグの 整形された文字列を 作成します。
844         *
845         * 内部データから生成された文字列に含まれる、[I] に、行番号を、
846         * [V] に、設定値を埋め込みます。
847         *
848         * これは、TagBuffer クラスとの互換性の為に用意されたメソッドです。
849         * 内部にキャッシュとして持つため、最初に一度実行すると、以降、同じ値しか返されません。
850         *
851         * 逆に、何度実行しても、内部変数は書き換えられませんので、
852         * 行番号と、設定値を替えて、連続で呼ぶことが可能です。
853         *
854         * @param       rowNo   行番号([I] 文字列を変換します)
855         * @param       val             設定値([V] 文字列を変換します)
856         *
857         * @return      行番号と設定値が置き換えられた文字列
858         */
859        public String makeTag( final int rowNo,final String val ) {
860                if( cacheTag == null ) {
861                        cacheTag = make().toString();
862                }
863
864                return cacheTag.replace( "[I]",String.valueOf( rowNo ) ).replace( "[V]",val );
865
866        //      String tag = makeBuf.toString();
867        //      tag = StringUtil.replace( tag,"[I]",String.valueOf( rowNo ) );
868        //      tag = StringUtil.replace( tag,"[V]",val );
869
870        //      return tag ;
871        }
872}