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