    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;
    020
    021/**
    022 * エレメントをあらわす、OGElement クラスを定義します。
    023 *
    024 * エレメントは、OGNode クラスを継承し、名称、属性、ノードリストを持つオブジェクトです。
    025 * 通常で言うところの、タグになります。
    026 * 属性は、OGAttributes クラスで管理します。ノードリスト に関する操作は、OGNodeクラスの実装です。
    027 *
    028 * OGNode は、enum OGNodeType で区別される状態を持っています。
    029 * OGNodeType は、それぞれ、再設定が可能です。
    030 * 例えば、既存のエレメントやノードに対して、コメントタイプ(Comment)を指定すると、
    031 * ファイル等への出力時にコメントとして出力されます。
    032 *
    033 * @og.rev 5.1.8.0 (2010/07/01) 新規作成
    034 *
    035 * @version  5.0
    036 * @author   Kazuhiko Hasegawa
    037 * @since    JDK6.0,
    038 */
    039public class OGElement extends OGNode {
    040//      public static final String CR  = System.getProperty("line.separator");
    041
    042        // 5.2.1.0 (2010/10/01) nameSpace 対応(og: や mis:)
    043        // 5.6.1.2 (2013/02/22) 個別に設定すべき。削除
    044        /** 特殊：内部的に、タグ属性の改行処理を行うタグ名の Stringを持っています。 **/
    045//      private static final String CR_SET = ":comment , :view , :select" ;
    046
    047        private final String            qName ;                         // このタグの名前(nameSpace も含むエレメントの名前)
    048//      private final int                       paraCnt;                        // 階層(-1は階層なし。1は改行のみ)
    049        private           OGAttributes  attri = null;           // 属性オブジェクト
    050
    051        // 階層に応じたスペースの設定
    052        private static final int      PARA_LEN  = 8;
    053        private static final String   PARA_CHAR = "\t";
    054        private static final String[] PARA = new String[PARA_LEN];
    055        static {
    056                PARA[0] = CR;
    057                StringBuilder buf = new StringBuilder();
    058                buf.append( CR );
    059                for( int i=1; i<PARA_LEN; i++ ) {
    060                        buf.append( PARA_CHAR );
    061                        PARA[i] = buf.toString();
    062                }
    063        }
    064
    065        /**
    066         * ノード名を指定してのトコンストラクター
    067         *
    068         * ノード名のみ指定するため、属性と、ノードリストが空のエレメントを構築します。
    069         *
    070         * @param       qName   ノード名
    071         */
    072        public OGElement( final String qName ) {
    073                this( qName,null );
    074        }
    075
    076        /**
    077         * ノード名と階層を指定してのトコンストラクター
    078         *
    079         * ノード名のみを持つ、属性と、ノードリストが空のエレメントを構築します。
    080         *
    081         * @param       qName   ノード名
    082         * @param       cnt             階層
    083         */
    084//      public OGElement( final String qName , final int cnt ) {
    085//              this( qName,(Attributes)null );
    086//      }
    087
    088        /**
    089         * ノード名を指定してのトコンストラクター
    090         *
    091         * ノード名のみ指定するため、属性と、ノードリストが空のエレメントを構築します。
    092         *
    093         * @og.rev 5.6.1.2 (2013/02/22) 廃止
    094         *
    095         * @param       qName   ノード名
    096         * @param       attri   属性オブジェクト
    097         */
    098//      public OGElement( final String qName , final OGAttributes attri ) {
    099//              super();
    100//              setNodeType( OGNodeType.Element );
    101//
    102//              if( qName == null ) {
    103//                      String errMsg = "エレメントには、ノード名は必須です。";
    104//                      throw new RuntimeException( errMsg );
    105//              }
    106//
    107//              boolean useCR = CR_SET.contains( qName );
    108//              if( useCR ) { attri.setUseCR( useCR ); }                // true の場合のみセットする。
    109//
    110//              this.qName   = qName;
    111//              this.attri   = attri ;
    112//              this.paraCnt = -1 ;
    113//      }
    114
    115        /**
    116         * ノード名、属性タブ、属性リストを指定してのトコンストラクター
    117         *
    118         * 注意 属性値の正規化は必ず行われます。
    119         * 属性値に含まれるCR(復帰), LF(改行), TAB(タブ)は、 半角スペースに置き換えられます。
    120         * XMLの規定では、属性の並び順は保障されませんが、SAXのAttributesは、XMLに記述された順番で
    121         * 取得できていますので、このクラスでの属性リストも、記述順での並び順になります。
    122         *
    123         * @og.rev 5.2.1.0 (2010/10/01) タグ属性の改行処理を、Set からString[] に変更。
    124         * @og.rev 5.6.1.2 (2013/02/22) CR_SET を配列から文字列に変更
    125         *
    126         * @param       qName   ノード名
    127         * @param       atts    属性リスト
    128         */
    129//      public OGElement( final String qName , final String attTab , final Attributes atts , final int cnt ) {
    130        public OGElement( final String qName , final Attributes atts ) {
    131                super();
    132                setNodeType( OGNodeType.Element );
    133
    134                if( qName == null ) {
    135                        String errMsg = "エレメントには、ノード名は必須です。";
    136                        throw new RuntimeException( errMsg );
    137                }
    138
    139                this.qName   = qName;
    140//              this.paraCnt = cnt ;
    141
    142                // 5.2.1.0 (2010/10/01) nameSpace の取得。CR_SET を SetからString[] に変更。
    143                // 5.6.1.2 (2013/02/22) 完全一致ではなく、indexOf の部分一致で反転するように変更。
    144//              boolean useCR = CR_SET.contains( qName );
    145
    146//              String attTab = getPara( paraCnt+1 );
    147//              this.attri = new OGAttributes( attTab , atts , useCR ) ;
    148                this.attri = new OGAttributes( atts ) ;
    149        }
    150
    151        /**
    152         * ノード名を返します。
    153         *
    154         * @return      ノード名
    155         */
    156        public String getTagName() {
    157                return qName;
    158        }
    159
    160        /**
    161         * 属性オブジェクトを返します。
    162         *
    163         * これは、org.xml.sax.Attributes ではなく、OGAttributes オブジェクトを返します。
    164         * 内部オブジェクトそのものを返しますので、この OGAttributes の変更は、この
    165         * エレメントが持つ内部属性も変更されます。
    166         *
    167         * @return      属性オブジェクト
    168         */
    169        public OGAttributes getOGAttributes() {
    170                return attri;
    171        }
    172
    173        /**
    174         * 属性オブジェクトをセットします。
    175         *
    176         * 属性オブジェクトのセットは、このメソッドからのみできるようにします。
    177         * 内部オブジェクトそのものにセットしますので、異なる OGAttributes をセットしたい場合は、
    178         * 外部で、コピーしてからセットしてください。
    179         *
    180         * @og.rev 5.6.1.2 (2013/02/22) 新規追加
    181         *
    182         * @param       attri 属性オブジェクト(org.opengion.fukurou.xml.OGAttributes)
    183         */
    184        public void setOGAttributes( final OGAttributes attri ) {
    185
    186//              boolean useCR = CR_SET.contains( qName );
    187//              attri.setUseCR( useCR );
    188//              String attTab = getPara( paraCnt+1 );
    189//              attri.setAttrTab( attTab );
    190
    191                this.attri = attri;
    192        }
    193
    194        /**
    195         * 属性リストから、id属性の、属性値を取得します。
    196         *
    197         * id属性 は、内部的にキャッシュしており、すぐに取り出せます。
    198         * タグを特定する場合、一般属性のキーと値で選別するのではなく、
    199         * id属性を付与して選別するようにすれば、高速に見つけることが可能になります。
    200         *
    201         * @og.rev 5.1.9.0 (2010/08/01) 新規追加
    202         *
    203         * @return      id属性値
    204         */
    205        public String getId() {
    206//              return attri.getId() ;
    207                return (attri != null) ? attri.getId() : null ;
    208        }
    209
    210        /**
    211         * 属性リストから、指定の属性キーの、属性値を取得します。
    212         *
    213         * この処理は、属性リストをすべてスキャンして、キーにマッチする
    214         * 属性オブジェクトを見つけ、そこから、属性値を取り出すので、
    215         * パフォーマンスに問題があります。
    216         * 基本的には、アドレス指定で、属性値を取り出すようにしてください。
    217         *
    218         * @og.rev 5.6.1.2 (2013/02/22) 新規追加
    219         *
    220         * @param       key     属性キー
    221         *
    222         * @return      属性値
    223         */
    224        public String getVal( final String key ) {
    225//              return attri.getVal( key );
    226                return (attri != null) ? attri.getVal( key ) : null ;
    227        }
    228
    229        /**
    230         * 属性リストに、属性(キー、値のセット)を設定します。
    231         *
    232         * 属性リストの一番最後に、属性(キー、値のセット)を設定します。
    233         *
    234         * @param       key     属性リストのキー
    235         * @param       val     属性リストの値
    236         */
    237        public void addAttr( final String key , final String val ) {
    238                if( attri == null ) { attri = new OGAttributes() ; }
    239                attri.add( key,val ) ;
    240        }
    241
    242        /**
    243         * 自分自身の状態が、指定の条件に合致しているかどうか、判定します。
    244         *
    245         * 合致している場合は、true を、合致していない場合は、false を返します。
    246         *
    247         * 指定の属性が null の場合は、すべてに合致すると判断します。
    248         * 例えば、kye のみ指定すると、その属性名を持っているエレメントすべてで
    249         * true が返されます。
    250         * 実行速度を考えると、ノード名は指定すべきです。
    251         *
    252         * @param       name    ノード名 null の場合は、すべての ノード名 に合致
    253         * @param       key     属性名 null の場合は、すべての 属性名 に合致
    254         * @param       val     属性値 null の場合は、すべての 属性値 に合致
    255         *
    256         * @return      条件がこのエレメントに合致した場合 true
    257         */
    258        public boolean match( final String name , final String key , final String val ) {
    259                // name が存在するが、不一致の場合は、false
    260                if( name != null && ! name.equals( qName ) ) { return false; }
    261
    262                // attri が null なのに、key か val が、null でない場合は合致しないので、false と判断
    263                if( attri == null && ( key != null || val != null ) ) { return false; }
    264
    265                // キーが存在し、値も存在する場合は、その値の合致と同じ結果となる。
    266                if( key != null ) {
    267                        if( val != null ) { return val.equals( attri.getVal( key ) ); }         // 値があれば、比較する。
    268                        else              { return ( attri.getAdrs( key ) >= 0 );     }              // 値がなければ、存在チェック
    269                }
    270
    271                // 値が存在する場合は、その値が含まれるかチェックし、あれば、true, なければ false
    272                if( val != null ) {
    273                        boolean flag = false;
    274                        int len = attri.size();
    275                        for( int i=0; i<len; i++ ) {
    276                                if( val.equals( attri.getVal(i) ) ) { flag = true; break; }
    277                        }
    278                        return flag;
    279                }
    280
    281                // 上記の条件以外は、すべてが null なので、true
    282                return true;
    283        }
    284
    285        /**
    286         * 段落文字列を返します。
    287         *
    288         * 段落文字列は、階層を表す文字列です。
    289         * 通常は　TAB ですが、XMLの階層が、PARA_LEN を超えても、段落を増やしません。
    290         * 段落の最初の文字は、改行です。
    291         *
    292         * @og.rev 5.6.1.2 (2013/02/22) 内部テキストがない場合のタグの終了時にスペースは入れない。
    293         * @og.rev 5.6.4.4 (2013/05/31) PARA_LEN を超えても、段落を増やしません。
    294         *
    295         * @param       cnt     階層(-1:なし。
    296         * @return      段落文字列
    297         * @see OGNodeType
    298         */
    299        private String getPara( final int cnt ) {
    300                if( cnt < 0 ) { return ""; }
    301                if( cnt < PARA_LEN ) {       return PARA[cnt]; }
    302                else {                                  return  PARA[PARA_LEN-1]; }                     // 5.6.4.4 (2013/05/31) PARA_LEN を超えても、段落を増やしません。
    303
    304//              StringBuilder buf = new StringBuilder();
    305//              buf.append( PARA[PARA_LEN-1] );
    306//              for( int i=PARA_LEN-1; i<cnt; i++ ) {
    307//                      buf.append( PARA_CHAR );
    308//              }
    309
    310//              return buf.toString();
    311        }
    312
    313        /**
    314         * オブジェクトの文字列表現を返します。
    315         *
    316         * 文字列は、OGNodeType により異なります。
    317         * Comment ノードの場合は、コメント記号を、Cdata ノードの場合は、CDATA を
    318         * つけて出力します。
    319         *
    320         * @og.rev 5.6.1.2 (2013/02/22) 内部テキストがない場合のタグの終了時にスペースは入れない。
    321         * @og.rev 5.6.4.4 (2013/05/31) 改行３つを改行２つに置換します。
    322         *
    323         * @param       cnt             Nodeの階層（-1:なし、0:改行のみ、1:改行＋"  "・・・・）
    324         * @return      このオブジェクトの文字列表現
    325         * @see OGNode#toString()
    326         */
    327//      public String toString() {
    328        @Override
    329        public String getText( final int cnt ) {
    330                StringBuilder buf = new StringBuilder();
    331
    332                buf.append( getPara(cnt) );
    333                buf.append( "<" ).append( qName );
    334
    335//              boolean useCR = CR_SET.contains( qName );
    336                buf.append( attri.getText( getPara(cnt+1) ) );
    337//              if( useCR ) { buf.append( getPara(cnt) ); }
    338
    339                String text = super.getText(cnt+1);
    340
    341                if( text.trim().isEmpty() ) {
    342//                      buf.append( " />" );
    343                        buf.append( "/>" );                                  // 5.6.1.2 (2013/02/22) タグの終了時にスペースは入れない。
    344                }
    345                else {
    346                        buf.append( ">" ).append( text );
    347                        buf.append( getPara(cnt) );
    348                        buf.append( "</" ).append( qName ).append( ">" );
    349                //      buf.append( CR );
    350                }
    351                String rtn = buf.toString();
    352
    353                switch( getNodeType() ) {
    354                        case Comment:   rtn = "<!-- "      + rtn + " -->"; break;
    355                        case Cdata:             rtn = "<![CDATA[ " + rtn + " ]]>"; break;
    356//                      case Text:
    357//                      case List:
    358                        default:                break;
    359                }
    360
    361//              return rtn ;
    362                return rtn.replaceAll( CR+CR+CR , CR+CR ) ;                     // 改行３つを改行２つに置換します。
    363        }
    364
    365        /**
    366         * オブジェクトの文字列表現を返します。
    367         *
    368         * 文字列は、OGNodeType により異なります。
    369         * Comment ノードの場合は、コメント記号を、Cdata ノードの場合は、CDATA を
    370         * つけて出力します。
    371         *
    372         * @og.rev 5.6.1.2 (2013/02/22) 廃止。継承元で処理。
    373         *
    374         * @return      このオブジェクトの文字列表現
    375         * @see OGNode#toString()
    376         */
    377//      @Override
    378//      public String toString() {
    379//              return getText(-10);
    380//      }
    381}