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