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