    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.opengion.fukurou.system.OgRuntimeException ;         // 6.4.2.0 (2016/01/29)
    019import org.xml.sax.Attributes;
    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
    041        private final String            qName   ;               // このタグの名前(nameSpace も含むエレメントの名前)
    042        private           OGAttributes  attri   ;               // 属性オブジェクト
    043
    044        // 階層に応じたスペースの設定
    045        private static final int      PARA_LEN  = 8;
    046        private static final String   PARA_CHAR = "\t";
    047        private static final String[] PARA = new String[PARA_LEN];
    048        static {
    049                PARA[0] = CR;
    050                final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
    051                buf.append( CR );
    052                for( int i=1; i<PARA_LEN; i++ ) {
    053                        buf.append( PARA_CHAR );
    054                        PARA[i] = buf.toString();
    055                }
    056        }
    057
    058        /**
    059         * ノード名を指定してのトコンストラクター
    060         *
    061         * ノード名のみ指定するため、属性と、ノードリストが空のエレメントを構築します。
    062         *
    063         * @param       qName   ノード名
    064         */
    065        public OGElement( final String qName ) {
    066                this( qName,null );
    067        }
    068
    069        /**
    070         * ノード名、属性タブ、属性リストを指定してのトコンストラクター
    071         *
    072         * 注意 属性値の正規化は必ず行われます。
    073         * 属性値に含まれるCR(復帰), LF(改行), TAB(タブ)は、 半角スペースに置き換えられます。
    074         * XMLの規定では、属性の並び順は保障されませんが、SAXのAttributesは、XMLに記述された順番で
    075         * 取得できていますので、このクラスでの属性リストも、記述順での並び順になります。
    076         *
    077         * @og.rev 5.2.1.0 (2010/10/01) タグ属性の改行処理を、Set からString[] に変更。
    078         * @og.rev 5.6.1.2 (2013/02/22) CR_SET を配列から文字列に変更
    079         *
    080         * @param       qName   ノード名
    081         * @param       atts    属性リスト
    082         */
    083        public OGElement( final String qName , final Attributes atts ) {
    084                super();
    085                setNodeType( OGNodeType.Element );
    086
    087                if( qName == null ) {
    088                        final String errMsg = "エレメントには、ノード名は必須です。";
    089                        throw new OgRuntimeException( errMsg );
    090                }
    091
    092                this.qName = qName;
    093                this.attri = new OGAttributes( atts ) ;
    094        }
    095
    096        /**
    097         * ノード名を返します。
    098         *
    099         * @return      ノード名
    100         */
    101        public String getTagName() {
    102                return qName;
    103        }
    104
    105        /**
    106         * 属性オブジェクトを返します。
    107         *
    108         * これは、org.xml.sax.Attributes ではなく、OGAttributes オブジェクトを返します。
    109         * 内部オブジェクトそのものを返しますので、この OGAttributes の変更は、この
    110         * エレメントが持つ内部属性も変更されます。
    111         *
    112         * @return      属性オブジェクト
    113         */
    114        public OGAttributes getOGAttributes() {
    115                return attri;
    116        }
    117
    118        /**
    119         * 属性オブジェクトをセットします。
    120         *
    121         * 属性オブジェクトのセットは、このメソッドからのみできるようにします。
    122         * 内部オブジェクトそのものにセットしますので、異なる OGAttributes をセットしたい場合は、
    123         * 外部で、コピーしてからセットしてください。
    124         *
    125         * @og.rev 5.6.1.2 (2013/02/22) 新規追加
    126         *
    127         * @param       attri 属性オブジェクト(org.opengion.fukurou.xml.OGAttributes)
    128         */
    129        public void setOGAttributes( final OGAttributes attri ) {
    130                this.attri = attri;
    131        }
    132
    133        /**
    134         * 属性リストから、id属性の、属性値を取得します。
    135         *
    136         * id属性 は、内部的にキャッシュしており、すぐに取り出せます。
    137         * タグを特定する場合、一般属性のキーと値で選別するのではなく、
    138         * id属性を付与して選別するようにすれば、高速に見つけることが可能になります。
    139         *
    140         * @og.rev 5.1.9.0 (2010/08/01) 新規追加
    141         *
    142         * @return      id属性値
    143         */
    144        public String getId() {
    145                // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..;
    146                return attri == null ? null : attri.getId() ;
    147        }
    148
    149        /**
    150         * 属性リストから、指定の属性キーの、属性値を取得します。
    151         *
    152         * この処理は、属性リストをすべてスキャンして、キーにマッチする
    153         * 属性オブジェクトを見つけ、そこから、属性値を取り出すので、
    154         * パフォーマンスに問題があります。
    155         * 基本的には、アドレス指定で、属性値を取り出すようにしてください。
    156         *
    157         * @og.rev 5.6.1.2 (2013/02/22) 新規追加
    158         *
    159         * @param       key     属性キー
    160         *
    161         * @return      属性値
    162         */
    163        public String getVal( final String key ) {
    164                // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..;
    165                return attri == null ? null : attri.getVal( key ) ;
    166        }
    167
    168        /**
    169         * 属性リストに、属性(キー、値のセット)を設定します。
    170         *
    171         * 属性リストの一番最後に、属性(キー、値のセット)を設定します。
    172         *
    173         * @param       key     属性リストのキー
    174         * @param       val     属性リストの値
    175         */
    176        public void addAttr( final String key , final String val ) {
    177                if( attri == null ) { attri = new OGAttributes() ; }
    178                attri.add( key,val ) ;
    179        }
    180
    181        /**
    182         * 自分自身の状態が、指定の条件に合致しているかどうか、判定します。
    183         *
    184         * 合致している場合は、true を、合致していない場合は、false を返します。
    185         *
    186         * 指定の属性が null の場合は、すべてに合致すると判断します。
    187         * 例えば、kye のみ指定すると、その属性名を持っているエレメントすべてで
    188         * true が返されます。
    189         * 実行速度を考えると、ノード名は指定すべきです。
    190         *
    191         * @param       name    ノード名 null の場合は、すべての ノード名 に合致
    192         * @param       key     属性名 null の場合は、すべての 属性名 に合致
    193         * @param       val     属性値 null の場合は、すべての 属性値 に合致
    194         *
    195         * @return      条件がこのエレメントに合致した場合 true
    196         */
    197        public boolean match( final String name , final String key , final String val ) {
    198                // name が存在するが、不一致の場合は、false
    199                if( name != null && ! name.equals( qName ) ) { return false; }
    200
    201                // attri が null なのに、key か val が、null でない場合は合致しないので、false と判断
    202                if( attri == null && ( key != null || val != null ) ) { return false; }
    203
    204                // キーが存在し、値も存在する場合は、その値の合致と同じ結果となる。
    205                if( key != null ) {
    206                        // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..;
    207                        return val == null
    208                                                ? attri.getAdrs( key ) >= 0                                     // 値がなければ、存在チェック
    209                                                : val.equals( attri.getVal( key ) ) ;           // 値があれば、比較する。
    210                }
    211
    212                // 値が存在する場合は、その値が含まれるかチェックし、あれば、true, なければ false
    213                if( val != null ) {
    214                        boolean flag = false;
    215                        final int len = attri.size();
    216                        for( int i=0; i<len; i++ ) {
    217                                if( val.equals( attri.getVal(i) ) ) { flag = true; break; }
    218                        }
    219                        return flag;
    220                }
    221
    222                // 上記の条件以外は、すべてが null なので、true
    223                return true;
    224        }
    225
    226        /**
    227         * 段落文字列を返します。
    228         *
    229         * 段落文字列は、階層を表す文字列です。
    230         * 通常は　TAB ですが、XMLの階層が、PARA_LEN を超えても、段落を増やしません。
    231         * 段落の最初の文字は、改行です。
    232         *
    233         * @og.rev 5.6.1.2 (2013/02/22) 内部テキストがない場合のタグの終了時にスペースは入れない。
    234         * @og.rev 5.6.4.4 (2013/05/31) PARA_LEN を超えても、段落を増やしません。
    235         *
    236         * @param       cnt     階層(-1:なし。
    237         * @return      段落文字列
    238         * @og.rtnNotNull
    239         * @see OGNodeType
    240         */
    241        private String getPara( final int cnt ) {
    242                // 6.4.1.1 (2016/01/16) PMD refactoring. A method should have only one exit point, and that should be the last statement in the method
    243                return cnt < 0
    244                                        ? ""
    245                                        : cnt < PARA_LEN
    246                                                ? PARA[cnt]
    247                                                : PARA[PARA_LEN-1] ;                    // 5.6.4.4 (2013/05/31) PARA_LEN を超えても、段落を増やしません。
    248        }
    249
    250        /**
    251         * オブジェクトの文字列表現を返します。
    252         *
    253         * 文字列は、OGNodeType により異なります。
    254         * Comment ノードの場合は、コメント記号を、Cdata ノードの場合は、CDATA を
    255         * つけて出力します。
    256         *
    257         * @og.rev 5.6.1.2 (2013/02/22) 内部テキストがない場合のタグの終了時にスペースは入れない。
    258         * @og.rev 5.6.4.4 (2013/05/31) 改行３つを改行２つに置換します。
    259         *
    260         * @param       cnt             Nodeの階層（-1:なし、0:改行のみ、1:改行＋"  "・・・・）
    261         * @return      このオブジェクトの文字列表現
    262         * @og.rtnNotNull
    263         * @see OGNode#toString()
    264         */
    265        @Override
    266        public String getText( final int cnt ) {
    267
    268                // 6.0.2.5 (2014/10/31) char を append する。
    269                final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE )
    270                        .append( getPara(cnt) )
    271                        .append( '<' ).append( qName )
    272                        .append( attri.getText( getPara(cnt+1) ) );
    273
    274                final String text = super.getText(cnt+1);
    275
    276                if( text.trim().isEmpty() ) {
    277                        buf.append( "/>" );                                     // 5.6.1.2 (2013/02/22) タグの終了時にスペースは入れない。       XML なので、このまま。
    278                }
    279                else {
    280                        buf.append( '>' ).append( text )
    281                                .append( getPara(cnt) )
    282                                .append( "</" ).append( qName ).append( '>' );
    283                        //      .append( CR );
    284                }
    285
    286                // 6.4.2.1 (2016/02/05) PMD refactoring. Prefer StringBuffer over += for concatenating strings
    287                switch( getNodeType() ) {
    288                        case Comment:   buf.insert( 0,"<!-- "      ).append( "-->"  ); break;
    289                        case Cdata:             buf.insert( 0,"<![CDATA[ " ).append( " ]]>" ); break;
    290        //              case Text:
    291        //              case List:
    292                        default:                break;
    293                }
    294
    295                return buf.toString().replaceAll( CR+CR+CR , CR+CR ) ;                  // 改行３つを改行２つに置換します。
    296        }
    297}