    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.xml;
    017
    018import org.xml.sax.Attributes;
    019import java.util.List;
    020import java.util.ArrayList;
    021
    022/**
    023 * 属性リストをあらわす、OGAttributes クラスを定義します。
    024 *
    025 * 属性リストは、キーと値のペアを、並び順で管理しているリストを保持しています。
    026 * 内部的には、 org.xml.sax.Attributes からの値の設定と、タブの属性の整列を行うための
    027 * 属性タブ、属性の改行の制御、属性の長さの制御 などを行います。
    028 *
    029 * @og.rev 5.1.8.0 (2010/07/01) 新規作成
    030 *
    031 * @version  5.0
    032 * @author   Kazuhiko Hasegawa
    033 * @since    JDK6.0,
    034 */
    035public class OGAttributes {
    036        /** システム改行コード **/
    037        public static final String      CR     = System.getProperty("line.separator");
    038
    039        /** 属性の個数制限。この個数で改行を行う。 {@value} */
    040        public static final int         CR_CNT = 4;
    041        /** 属性の長さ制限。これ以上の場合は、改行を行う。 {@value} */
    042        public static final int         CR_LEN = 80;
    043
    044        private final List<OGAtts>        attList = new ArrayList<OGAtts>();
    045
    046//      private String  attTab  = CR + "  ";            // タグの前方スペース
    047//      private String  attTab  = "  ";                         // 属性の前方スペース
    048        private boolean useCR   = false;                        // 属性の改行出力を行うかどうか。個数制限が１と同じ
    049        private int     maxValLen       = 0;                            // 属性の名前の最大文字数
    050        private String  id              = null;                         // 特別な属性。id で検索を高速化するため。
    051
    052        /**
    053         * デフォルトトコンストラクター
    054         *
    055         * 取りあえず、属性オブジェクトを構築する場合に使用します。
    056         * 属性タブは、改行＋タブ 、属性リストは、空のリスト、属性改行は、false を初期設定します。
    057         *
    058         */
    059        public OGAttributes() {
    060                // Document empty method チェック対策
    061        }
    062
    063        /**
    064         * 属性タブ、属性リスト、属性改行の有無を指定してのトコンストラクター
    065         *
    066         * 属性タブ、属性リストに null を指定すると、デフォルトトコンストラクターの設定と
    067         * 同じ状態になります。
    068         *
    069         * 注意 属性値の正規化は必ず行われます。
    070         * 属性値に含まれるCR(復帰), LF(改行), TAB(タブ)は、 半角スペースに置き換えられます。
    071         * XMLの規定では、属性の並び順は保障されませんが、SAXのAttributesは、XMLに記述された順番で
    072         * 取得できていますので、このクラスでの属性リストも、記述順での並び順になります。
    073         *
    074         * @og.rev 5.1.9.0 (2010/08/01) id 属性のみ特別にキャッシュしておく。
    075         *
    076         * @param attri         属性リスト
    077         */
    078//      public OGAttributes( final String attTab , final Attributes attri , final boolean useCR ) {
    079        public OGAttributes( final Attributes attri ) {
    080//              if( attTab != null ) {
    081////                    this.attTab = CR + attTab + "  ";
    082//                      this.attTab = attTab;
    083//              }
    084//              this.useCR  = useCR;
    085
    086                int num = (attri == null)? 0 : attri.getLength();
    087                int maxLen = 0;
    088                for (int i = 0; i < num; i++) {
    089//                      OGAtts atts = new OGAtts( attri.getQName(i),attri.getValue(i) );
    090                        String key = attri.getQName(i);
    091                        String val = attri.getValue(i);
    092                        OGAtts atts = new OGAtts( key,val );
    093                        attList.add( atts );
    094                        maxLen = atts.maxKeyLen( maxLen );
    095
    096                        if( "id".equals( key ) ) { id = val; }          // 5.1.9.0 (2010/08/01)
    097                }
    098
    099                maxValLen = maxLen;
    100        }
    101
    102        /**
    103         * 属性タブを設定します。
    104         *
    105         * 属性タブは、自身のエレメントの書き出し位置までの文字列を持っています。
    106         * 属性を、そのまま、タグの横に出すと、１行に収まらないケースがあります。
    107         * その場合、自身の位置＋タブを属性の書き出しに使用します。
    108         * その文字列を定義しています。
    109         *
    110         * 引数は、自身のエレメントの書き出し位置です。
    111         *
    112         * @og.rev 5.1.9.0 (2010/08/01) 廃止
    113         *
    114         * 内部的に、｢CR + attTab + タブ｣ に加工されます。
    115         *
    116         * @param       attTab  属性タブ
    117         */
    118//      public void setAttrTab( final String attTab ) {
    119//              if( attTab != null ) {
    120////                    this.attTab = CR + attTab + "\t";
    121//                      this.attTab = attTab;
    122//              }
    123//      }
    124
    125        /**
    126         * 属性改行の有無を設定します。
    127         *
    128         * タグによって、属性が多くなったり、意味が重要な場合は、属性１つづつに改行を
    129         * 行いたいケースがあります。
    130         * 属性改行をtrue に設定すると、属性一つづつで、改行を行います。
    131         *
    132         * false の場合は、自動的な改行処理が行われます。
    133         * これは、属性の個数制限(CR_CNT)を超える場合は、改行を行います。
    134         *
    135         * @param       useCR   属性改行の有無(true:１属性単位の改行)
    136         * @see #CR_CNT
    137         * @see #CR_LEN
    138         */
    139        public void setUseCR( final boolean useCR ) {
    140                this.useCR  = useCR;
    141        }
    142
    143        /**
    144         * 属性リストの個数を取得します。
    145         *
    146         * @return      属性リストの個数
    147         */
    148        public int size() {
    149                return attList.size();
    150        }
    151
    152        /**
    153         * 属性リストから、指定の配列番号の、属性キーを取得します。
    154         *
    155         * @param       adrs    配列番号
    156         *
    157         * @return      属性キー
    158         */
    159        public String getKey( final int adrs ) {
    160                return attList.get(adrs).KEY ;
    161        }
    162
    163        /**
    164         * 属性リストから、指定の配列番号の、長さ補正が行われた属性キーを取得します。
    165         *
    166         * useCR=true の場合に、属性の改行が行われますが、そのときに、キーが縦に並びます。
    167         * そして、値も縦に並ぶため、間の ｢＝｣記号の位置をそろえて、表示します。
    168         * 属性リストの最大長さ＋１ になるように、キーの文字列にスペースを埋めます。
    169         * これにより、属性を改行して表示しても、値の表示位置がそろいます。
    170         *
    171         * @param       adrs    配列番号
    172         *
    173         * @return      属性リスト群の長さ補正が行われた、属性キー＋空白文字列
    174         */
    175//      private String getAlignKey( final int adrs ) {
    176//              return attList.get(adrs).KEY + SPACE.substring( attList.get(adrs).LEN,maxValLen ) ;
    177//      }
    178
    179        /**
    180         * 属性リストから、指定の配列番号の、属性値を取得します。
    181         *
    182         * @param       adrs    配列番号
    183         *
    184         * @return      属性値
    185         */
    186        public String getVal( final int adrs ) {
    187                return attList.get(adrs).VAL ;
    188        }
    189
    190        /**
    191         * 属性リストから、指定の属性キーの、属性値を取得します。
    192         *
    193         * この処理は、属性リストをすべてスキャンして、キーにマッチする
    194         * 属性オブジェクトを見つけ、そこから、属性値を取り出すので、
    195         * パフォーマンスに問題があります。
    196         * 基本的には、アドレス指定で、属性値を取り出すようにしてください。
    197         *
    198         * @og.rev 5.1.9.0 (2010/08/01) 新規追加
    199         *
    200         * @param       key     属性キー
    201         *
    202         * @return      属性値
    203         */
    204        public String getVal( final String key ) {
    205                String val = null;
    206
    207                if( key != null ) {
    208                        for( OGAtts atts : attList ) {
    209                                if( key.equals( atts.KEY ) ) {
    210                                        val = atts.VAL;
    211                                        break;
    212                                }
    213                        }
    214                }
    215
    216                return val;
    217        }
    218
    219        /**
    220         * 属性リストから、指定の属性キーの、アドレスを取得します。
    221         *
    222         * どちらかというと、キーの存在チェックに近い処理を行います。
    223         * この処理は、属性リストをすべてスキャンして、キーにマッチする
    224         * 属性オブジェクトを見つけ、そこから、属性値を取り出すので、
    225         * パフォーマンスに問題があります。
    226         *
    227         * @og.rev 5.1.9.0 (2010/08/01) 新規追加
    228         *
    229         * @param       key     属性キー
    230         *
    231         * @return      アドレス キーが存在しない場合は、-1 を返す。
    232         */
    233        public int getAdrs( final String key ) {
    234                int adrs = -1;
    235
    236                if( key != null ) {
    237                        for( int i=0; i<attList.size(); i++ ) {
    238                                if( key.equals( attList.get(i).KEY ) ) {
    239                                        adrs = i;
    240                                        break;
    241                                }
    242                        }
    243                }
    244
    245                return adrs;
    246        }
    247
    248        /**
    249         * 属性リストから、id属性の、属性値を取得します。
    250         *
    251         * id属性 は、内部的にキャッシュしており、すぐに取り出せます。
    252         * タグを特定する場合、一般属性のキーと値で選別するのではなく、
    253         * id属性を付与して選別するようにすれば、高速に見つけることが可能になります。
    254         *
    255         * @og.rev 5.1.9.0 (2010/08/01) 新規追加
    256         *
    257         * @return      id属性値
    258         */
    259        public String getId() { return id; }
    260
    261        /**
    262         * 属性リストの、指定の配列番号に、属性値を設定します。
    263         *
    264         * @og.rev 5.1.9.0 (2010/08/01) id 属性のみ特別にキャッシュしておく。
    265         *
    266         * @param       adrs    配列番号
    267         * @param       val     属性値
    268         */
    269        public void setVal( final int adrs , final String val ) {
    270                OGAtts atts = attList.remove(adrs) ;
    271                attList.add( adrs , new OGAtts( atts.KEY,val ) );
    272
    273                if( "id".equals( atts.KEY ) ) { id = val; }             // 5.1.9.0 (2010/08/01)
    274        }
    275
    276        /**
    277         * 属性リストに、指定のキー、属性値を設定します。
    278         * もし、属性リストに、指定のキーがあれば、属性値を変更します。
    279         * なければ、最後に追加します。
    280         *
    281         * @og.rev 5.6.1.2 (2013/02/22) 新規追加
    282         *
    283         * @param       key     属性キー
    284         * @param       val     属性値
    285         */
    286        public void setVal( final String key , final String val ) {
    287                int adrs = getAdrs( key );
    288                if( adrs < 0 ) { add( key,val ); }
    289                else           { setVal( adrs,val ); }
    290        }
    291
    292        /**
    293         * 属性リストに、属性(キー、値のセット)を設定します。
    294         *
    295         * 属性リストの一番最後に、属性(キー、値のセット)を設定します。
    296         *
    297         * @og.rev 5.1.9.0 (2010/08/01) id 属性のみ特別にキャッシュしておく。
    298         *
    299         * @param       key     属性リストのキー
    300         * @param       val     属性リストの値
    301         */
    302        public void add( final String key , final String val ) {
    303
    304                OGAtts atts = new OGAtts( key,val );
    305                attList.add( atts );
    306                maxValLen = atts.maxKeyLen( maxValLen );
    307
    308                if( "id".equals( key ) ) { id = val; }          // 5.1.9.0 (2010/08/01)
    309        }
    310
    311        /**
    312         * 指定のアドレスの属性リストに、属性(キー、値のセット)を設定します。
    313         *
    314         * 指定のアドレスの属性を置き換えるのではなく追加します。
    315         *
    316         * @og.rev 5.1.9.0 (2010/08/01) id 属性のみ特別にキャッシュしておく。
    317         *
    318         * @param       adrs    属性リストのアドレス
    319         * @param       key     属性リストのキー
    320         * @param       val     属性リストの値
    321         */
    322        public void add( final int adrs , final String key , final String val ) {
    323
    324                OGAtts atts = new OGAtts( key,val );
    325                attList.add( adrs , atts );
    326                maxValLen = atts.maxKeyLen( maxValLen );
    327
    328                if( "id".equals( key ) ) { id = val; }          // 5.1.9.0 (2010/08/01)
    329        }
    330
    331        /**
    332         * 指定のアドレスの属性リストから、属性情報を削除します。
    333         *
    334         * 指定のアドレスの属性を置き換えるのではなく追加します。
    335         *
    336         * @og.rev 5.1.9.0 (2010/08/01) id 属性のみ特別にキャッシュしておく。
    337         *
    338         * @param       adrs    属性リストのアドレス
    339         */
    340        public void remove( final int adrs ) {
    341//              attList.remove(adrs) ;
    342
    343                OGAtts atts = attList.remove(adrs) ;
    344
    345                if( "id".equals( atts.KEY ) ) { id = null; }            // 5.1.9.0 (2010/08/01)
    346
    347                // 削除したキーが maxValLen だったとしても、再計算は、行いません。
    348                // 再計算とは、次の長さを見つける必要があるので、すべての OGAtts をもう一度
    349                // チェックする必要が出てくるためです。
    350        }
    351
    352        /**
    353         * オブジェクトの文字列表現を返します。
    354         *
    355         * 属性については、並び順は、登録順が保障されます。
    356         *
    357         * 属性は、３つのパターンで文字列化を行います。
    358         *  ・useCR=true の場合
    359         *     この場合は、属性を１行ずつ改行しながら作成します。属性キーは、
    360         *     最大長＋１ でスペース埋めされて、整形されます。
    361         *  ・useCR=false の場合
    362         *     ・属性の個数制限(CR_CNT)単位に、改行が行われます。
    363         *       これは、属性が右に多く並びすぎるのを防ぎます。
    364         *     ・属性の長さ制限(CR_LEN)単位で、改行が行われます。
    365         *       これは、たとえ、属性の個数が少なくても、文字列として長い場合は、
    366         *       改行させます。
    367         *
    368         * @og.rev 5.6.1.2 (2013/02/22) 改行処理の修正。最後の属性処理の後にも改行を入れる。
    369         * @og.rev 5.6.4.4 (2013/05/31) 改行処理の修正。attTabが、ゼロ文字列の場合の対応。
    370         *
    371         * @param       attTab  Nodeの階層を表す文字列。
    372         * @return      このオブジェクトの文字列表現
    373         * @see OGNode#toString()
    374         * @see #setUseCR( boolean )
    375         */
    376//      @Override
    377//      public String toString() {
    378        public String getText( final String attTab ) {
    379                StringBuilder buf = new StringBuilder();
    380
    381                String crTab = (attTab.length() > 0) ? attTab : CR + "\t" ;
    382
    383                if( useCR ) {
    384                        for( int i=0; i<size(); i++ ) {
    385                                OGAtts atts = attList.get(i);
    386//                              buf.append( attTab );
    387                                buf.append( crTab );
    388                                buf.append( atts.getAlignKey( maxValLen ) ).append( " = " ).append( atts.QRT_VAL );
    389                        }
    390                        // 5.6.1.2 (2013/02/22) 改行処理の修正。最後の属性処理の後にも改行を入れる。
    391                        buf.append( CR );
    392                }
    393                else {
    394                        int crCnt = 0;
    395                        int crLen = 0;
    396                        for( int i=0; i<size(); i++ ) {
    397                                OGAtts atts = attList.get(i);
    398                                crCnt++ ;
    399                                crLen += atts.LEN;
    400                                if( i>0 && (crCnt > CR_CNT || crLen > CR_LEN) ) {
    401//                                      buf.append( attTab );
    402                                        buf.append( crTab );
    403                                        buf.append( atts.KEY ).append( "=" ).append( atts.QRT_VAL );
    404                                        crCnt = 0;
    405                                        crLen = 0;
    406                                }
    407                                else {
    408                                        buf.append( " " ).append( atts.KEY ).append( "=" ).append( atts.QRT_VAL );
    409                                }
    410                        }
    411//                      buf.append( " " );
    412                }
    413
    414                return buf.toString();
    415        }
    416
    417        /**
    418         * オブジェクトの文字列表現を返します。
    419         *
    420         * @og.rev 5.6.1.2 (2013/02/22) 改行処理の修正。最後の属性処理の後にも改行を入れる。
    421         *
    422         * @return      このオブジェクトの文字列表現
    423         * @see OGNode#toString()
    424         */
    425        @Override
    426        public String toString() {
    427                return getText( " " );
    428        }
    429}
    430
    431/**
    432 * 属性キーと属性値を管理する クラス
    433 *
    434 * 属性自身は、属性キーと属性値のみで十分ですが、改行処理や文字列の長さ設定で、
    435 * 予め内部処理をしておきたいため、クラス化しています。
    436 *
    437 * 内部変数は、final することで定数化し、アクセスメソッド経由ではなく、直接内部変数を
    438 * 参照させることで、見易さを優先しています。
    439 */
    440final class OGAtts {
    441        /** 属性の長さをそろえるための空白文字の情報 **/
    442        private static final String     SPACE  = "                    ";        // 5.1.9.0 (2010/09/01) public ⇒ private へ変更
    443
    444        /** 属性キー **/
    445        public          final String  KEY ;
    446        /** 属性値 **/
    447        public          final String  VAL ;
    448        private         final int     KLEN ;
    449                                final int     LEN ;
    450                                final String  QRT_VAL;
    451
    452        /**
    453         * 引数を指定して構築する、コンストラクター
    454         *
    455         * 属性キーと、属性値 を指定して、オブジェクトを構築します。
    456         *
    457         * @param       key     属性キー
    458         * @param       val     属性値
    459         */
    460        public OGAtts( final String key , final String val ) {
    461                KEY  = key;
    462//              VAL  = (val == null) ? "" : val ;
    463                VAL  = (val == null) ? "" : htmlFilter(val) ;
    464                KLEN = key.length();
    465                LEN  = KLEN + VAL.length();
    466
    467                QRT_VAL = ( VAL.indexOf( '"' ) >= 0 ) ? "'" + VAL + "'" : "\"" + VAL + "\"" ;
    468        }
    469
    470        /**
    471         * キーの文字長さの比較で、大きい数字を返します。
    472         *
    473         * 属性キーの最大の文字列長を求めるため、引数の長さと、属性キーの長さを比較して、
    474         * 大きな値の方を返します。
    475         * この処理を、属性すべてに行えば、最終的に最も大きな値が残ることになります。
    476         *
    477         * @param       maxLen  属性キーの最大長さ
    478         *
    479         * @return      属性リスト群の長さ補正が行われた、属性キー＋空白文字列
    480         */
    481        protected int maxKeyLen( final int maxLen ) {
    482                return (maxLen > KLEN) ? maxLen : KLEN ;
    483        }
    484
    485        /**
    486         * 長さ補正が行われた属性キーを取得します。
    487         *
    488         * useCR=true の場合に、属性の改行が行われますが、そのときに、キーが縦に並びます。
    489         * そして、値も縦に並ぶため、間の ｢＝｣記号の位置をそろえて、表示します。
    490         * 属性リストの最大長さ＋１ になるように、キーの文字列にスペースを埋めます。
    491         * これにより、属性を改行して表示しても、値の表示位置がそろいます。
    492         *
    493         * @param       maxLen  属性キーの最大長さ
    494         *
    495         * @return      属性リスト群の長さ補正が行われた、属性キー＋空白文字列
    496         */
    497        protected String getAlignKey( final int maxLen ) {
    498                return KEY + SPACE.substring( KLEN,maxLen ) ;
    499        }
    500
    501        /**
    502         * HTML上のエスケープ文字を変換します。
    503         *
    504         * HTMLで表示する場合にきちんとエスケープ文字に変換しておかないと
    505         * Script を実行されたり、不要なHTMLコマンドを潜り込まされたりするため、
    506         * セキュリティーホールになる可能性があるので、注意してください。
    507         *
    508         * ※ オリジナルは、org.opengion.fukurou.util.StringUtil#htmlFilter( String )
    509         * ですが、ダブルクオート、シングルクオートの変換処理を省いています。
    510         *
    511         * @param       input HTMLエスケープ前の文字列
    512         *
    513         * @return      エスケープ文字に変換後の文字列
    514         * @see  org.opengion.fukurou.util.StringUtil#htmlFilter( String )
    515         */
    516        private String htmlFilter( final String input ) {
    517                if( input == null || input.length() == 0 ) { return ""; }
    518                StringBuilder rtn = new StringBuilder();
    519                char ch;
    520                for(int i=0; i<input.length(); i++) {
    521                        ch = input.charAt(i);
    522                        switch( ch ) {
    523                                case '<'  : rtn.append("&lt;");   break;
    524                                case '>'  : rtn.append("&gt;");   break;
    525                //              case '"'  : rtn.append("&quot;"); break;
    526                //              case '\'' : rtn.append("&#39;");  break;
    527                                case '&'  : rtn.append("&amp;");  break;
    528                                default   : rtn.append(ch);
    529                        }
    530                }
    531                return( rtn.toString() );
    532        }
    533}