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