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) 改行3つを改行2つに置換します。
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 ) ;                     // 改行3つを改行2つに置換します。
286        }
287}