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 java.util.List;
019import java.util.ArrayList;
020
021/**
022 * ノードの基底クラスとなる、OGNode クラスを定義します。
023 *
024 * OGElement、OGDocument は、この、OGNode クラスを継承します。
025 * ただし、OGAttributes は、独立しているため、このクラスは継承していません。
026 *
027 * 最も一般的なノードは、テキストノードであり、
028 *
029 * OGNode は、enum OGNodeType で区別される状態を持っています。
030 * その内、OGElement と OGDocument は、サブクラスになっています。
031 * OGNodeType は、それぞれ、再設定が可能です。
032 * 例えば、既存のエレメントやノードに対して、コメントタイプ(Comment)を指定すると、
033 * ファイル等への出力時にコメントとして出力されます。
034 *
035 *   List   :内部に、OGNode の ArrayList を持つ
036 *   Text   :内部は、文字列の BODY 部分を持つ
037 *   Comment  :内部は、文字列であるが、toString() 時には、コメント記号を前後に出力する。
038 *   Cdata   :内部は、TextNodeのArrayList を持つ、toString() 時には、Cdataを前後に出力する。
039 *   Element  :タグ名、属性、OGNode の ArrayList の入れ子状態をもつ
040 *   Document :トップのElement として、read/write するときに使用。構造は、唯一の OGElement を持つ List タイプ
041 *
042 * @og.rev 5.1.8.0 (2010/07/01) 新規作成
043 * @og.rev 5.6.1.2 (2013/02/22) 構想からやり直し
044 *
045 * @version  5.0
046 * @author   Kazuhiko Hasegawa
047 * @since    JDK6.0,
048 */
049public class OGNode {
050        public static final String CR  = System.getProperty("line.separator");
051
052        private final List<OGNode> nodes = new ArrayList<OGNode>();             // ノードリスト
053        private final String    text;                                                                   // テキストノード用の文字列ノード値
054        private OGNodeType              nodeType ;                                                              // List,Text,Comment,Cdata,Element,Document
055        private OGNode                  parentNode = null;                                              // 自身の親ノード(ただし、最終セットされたノード)
056
057        /**
058         * デフォルトコンストラクター
059         *
060         * ここでは、NodeType は、List に設定されます。
061         */
062        public OGNode() {
063                this.text  = null;
064                nodeType   = OGNodeType.List;
065        }
066
067        /**
068         * テキストノードを構築するためのコンストラクター
069         *
070         * テキストノードは、簡易的に、内部には、ノードリストではなく文字列を持っています。
071         *
072         * @og.rev 5.6.1.2 (2013/02/22) 内部テキストがない場合のタグの終了時にスペースは入れない。
073         *
074         * ここでは、NodeType は、Text に設定されます。
075         * ただし、引数のテキストが null のNodeType は、List に設定されます。
076         *
077         * @param       txt     テキストノードの設定値
078         */
079        public OGNode( final String txt ) {
080                text = txt ;
081                if( text != null )      { nodeType = OGNodeType.Text; }
082                else                            { nodeType = OGNodeType.List; }
083        }
084
085        /**
086         * テキストノードをノードリストに追加します。
087         *
088         * 内部的にテキストノードを構築して、リストに追加しています。
089         * 戻り値は、StringBuilder#append(String) の様に、連結登録できるように
090         * 自分自身を返しています。
091         * テキストノードに、この処理を行うと、エラーになります。
092         * 一旦、テキストノードとして作成したノードには、ノードを追加できません。
093         *
094         * @param       txt     テキストノードの設定値
095         *
096         * @return      自分自身(this)のノード
097         */
098        public OGNode addNode( final String txt ) {
099                if( txt != null ) {
100                        if( nodeType == OGNodeType.Text ) {
101                                // テキストノードにノードは追加できません。
102                                String errMsg = "一旦、テキストノードとして作成したノードには、ノードを追加できません。";
103                                throw new RuntimeException( errMsg );
104                        }
105
106                        OGNode node = new OGNode( txt );
107                        node.parentNode = this;
108                        nodes.add( node );
109                }
110                return this;
111        }
112
113        /**
114         * ノードをノードリストに追加します。
115         *
116         * 追加するノードの親として、自分自身を登録します。
117         * なお、同じオブジェクトを、複数の親に追加する場合(ノードリストには追加可能)は、
118         * 親ノードは、最後に登録されたノードのみが設定されます。
119         * テキストノードに、この処理を行うと、エラーになります。
120         * 一旦、テキストノードとして作成したノードには、ノードを追加できません。
121         *
122         * @param       node    ノード
123         *
124         * @return      自分自身(this)のノード
125         */
126        public OGNode addNode( final OGNode node ) {
127                if( node != null ) {
128                        if( nodeType == OGNodeType.Text ) {
129                                // テキストノードにノードは追加できません。
130                                String errMsg = "一旦、テキストノードとして作成したノードには、ノードを追加できません。";
131                                throw new RuntimeException( errMsg );
132                        }
133
134                        node.parentNode = this;
135                        nodes.add( node );
136                }
137                return this;
138        }
139
140        /**
141         * ノードリストに追加されている、ノードの個数を返します。
142         *
143         * @return      ノードリストの数
144         */
145        public int nodeSize() {
146                return nodes.size();
147        }
148
149        /**
150         * ノードリストに追加されている、ノードを返します。
151         *
152         * ノードの指定には、配列番号を使用します。
153         * ノードの個数は、事前に、nodeSize() で調べて置いてください。
154         * 当然、テキストノードの場合は、nodeSize()==0 なので、
155         * このメソッドでは取得できません。
156         *
157         * @param       adrs    ノードリストの位置
158         *
159         * @return      指定の配列番号のノード
160         */
161        public OGNode getNode( final int adrs ) {
162                return nodes.get(adrs);
163        }
164
165        /**
166         * ノードリストに、ノードをセットします。
167         *
168         * ノードリストの指定のアドレスに、ノードをセットします。
169         * これは、追加ではなく置換えになります。
170         * ノードの指定には、配列番号を使用します。
171         * ノードの個数は、事前に、nodeSize() で調べて置いてください。
172         *
173         * @param       adrs    ノードリストの位置
174         * @param       node    セットするノード
175         */
176        public void setNode( final int adrs , final OGNode node ) {
177                nodes.set(adrs,node);
178        }
179
180        /**
181         * 自身にセットされている、親ノードを返します。
182         *
183         * 親ノードは、自身のオブジェクトに、一つしか設定できません。
184         * これは、オブジェクトとして、同一ノードを、複数の親ノードに
185         * 追加した場合(これは、ノードリストへの追加なので可能)最後に追加した
186         * 親ノードのみ、保持していることになります。
187         * XML を構築するときは、同一のノードであっても、毎回、作成しなおさないと、
188         * 親ノードを見つけて、何かを行う場合には、おかしな動きをすることになります。
189         * なお、ノードオブジェクト自体が、親ノードから削除されても、自身の
190         * 親ノード情報は保持し続けています。
191         * ある Element から削除したノードを別のElementに追加すると、その時点で、
192         * 親ノードも更新されます。
193         *
194         * @return      親ノード
195         */
196        public OGNode getParentNode() {
197                return parentNode;
198        }
199
200        /**
201         * 自身にセットされている、親ノードの階層数を返します。
202         *
203         * 自身のオブジェクトに設定されている親ノードを順番にさかのぼって、
204         * 何階層あるか返します。
205         * これは、getText(int) の引数に使えます。
206         * 親ノードがひとつもない場合、つまり自身が最上位の場合は、0 が返されます。
207         *
208         * @return      自身の階層
209         */
210        public int getParentCount() {
211                int para = 0;
212                OGNode node = getParentNode();
213                while( node != null ) {
214                        para++ ;
215                        node = node.getParentNode();
216                }
217                return para;
218        }
219
220        /**
221         * ノードリストから、指定の配列番号の、ノードを削除します。
222         *
223         * ノードの指定には、配列番号を使用します。
224         * ノードの個数は、事前に、nodeSize() で調べて置いてください。
225         *
226         * @param       adrs    ノードリストの位置
227         *
228         * @return      削除されたノード
229         */
230        public OGNode removeNode( final int adrs ) {
231                return nodes.remove(adrs);
232        }
233
234        /**
235         * ノードリストから、すべてのノードを削除します。
236         *
237         * これは、ノードリストをクリアします。
238         *
239         */
240        public void clearNode() {
241                nodes.clear();
242        }
243
244        /**
245         * ノードリストから、指定のノード(orgNode)を新しいノード(newNode)に置き換えます。
246         *
247         * ノードは、それぞれ、ノードが作成された順番で、ユニークな番号を持っています。
248         * その番号を元に、ノードを探し出して、置き換えます。
249         * 通常の、XMLパースから作成されたノードは、すべて一意にユニーク番号が振られますが、
250         * 新しくつったノードを複数のノードと置き換える場合、置き換えられた後のノードは、
251         * オブジェクトそのものが、同一になるため、注意が必要です。
252         *
253         * @param       orgNode 置換元のオリジナルノード
254         * @param       newNode 置換する新しいノード
255         */
256        public void changeNode( final OGNode orgNode , final OGNode newNode ) {
257                int size = nodes.size();
258                for( int i=0; i<size; i++ ) {
259                        OGNode node = nodes.get(i);
260                        if( node.equals( orgNode ) ) {          // Object.equals なので、オブジェクトそのものの一致判定
261                                nodes.set( i,newNode );
262                        }
263                        else {
264                                node.changeNode( orgNode,newNode );
265                        }
266                }
267        }
268
269        /**
270         * ノードリストから、直下(メンバー)のエレメントのみをリストにして返します。
271         *
272         * ノードリストの第一レベルで、エレメントのみを返します。
273         * 通常は、あるエレメントを、getElementList( String ) 等で検索した後、その子要素を
274         * 取り出す場合に使用します。
275         * 該当するエレメントが、なにも存在しない場合は、空のリストオブジェクトが返されます。
276         *
277         * @return      直下(メンバー)のエレメントのリスト
278         */
279        public List<OGElement> getChildElementList() {
280                List<OGElement> eles = new ArrayList<OGElement>();
281
282                for( OGNode node : nodes ) {
283                        if( node.nodeType == OGNodeType.Element ) {
284                                eles.add( (OGElement)node );
285                        }
286                }
287
288                return eles;
289        }
290
291        /**
292         * ノードリストから、下位の階層に存在するすべてのエレメントをリストにして返します。
293         *
294         * エレメントは、名前を指定して検索します。
295         * 該当するエレメントが、なにも存在しない場合は、空のリストオブジェクトが返されます。
296         *
297         * @param       qName   エレメントの名前
298         *
299         * @return      下位の階層に存在するすべてのエレメントのリスト
300         */
301        public List<OGElement> getElementList( final String qName ) {
302                List<OGElement> eles = new ArrayList<OGElement>();
303
304                if( qName != null ) {
305                        for( OGNode node : nodes ) {
306                                if( node.nodeType == OGNodeType.Element ) {
307                                        OGElement ele = (OGElement)node;
308                                        if( qName.equals( ele.getTagName() ) ) {
309                                                eles.add( ele );
310                                        }
311                                        eles.addAll( ele.getElementList( qName ) );
312                                }
313                        }
314                }
315
316                return eles;
317        }
318
319        /**
320         * ノードタイプを設定します。
321         *
322         * ノードタイプとは、List , Text , Comment , Cdata , Element , Document などの
323         * ノードの種別を表す enum タイプです。
324         * 基本的には、オブジェクトの取得時に、ファクトリメソッド経由であれば、自動的に設定
325         * されています。
326         * ここでは、可変設定できます。
327         * 例えば、既存のエレメントやノードに対して、コメントタイプ(Comment)を指定すると、
328         * ファイル等への出力時にコメントとして出力されます。
329         * null を指定すると、なにも処理されません。
330         *
331         * @param       type    enumのOGNodeType
332         * @see OGNodeType
333         */
334        public void setNodeType( final OGNodeType type ) {
335                if( type != null ) {
336                        if( type != OGNodeType.Text && nodeType == OGNodeType.Text ) {
337                                OGNode node = new OGNode( text );
338                                node.parentNode = this;
339                                nodes.add( node );
340                        }
341
342                        nodeType = type ;
343                }
344        }
345
346        /**
347         * ノードタイプを取得します。
348         *
349         * ノードタイプとは、List , Text , Comment , Cdata , Element , Document などの
350         * ノードの種別を表す enum タイプです。
351         * 基本的には、オブジェクトの取得時に、ファクトリメソッド経由であれば、自動的に設定
352         * されています。
353         *
354         * @return      ノードタイプ
355         * @see OGNodeType
356         */
357        public OGNodeType getNodeType() {
358                return nodeType;
359        }
360
361        /**
362         * ノードリストの文字列を返します。
363         *
364         * これは、タグで言うところのBODY部に書かれた文字列に相当します。
365         * 該当する文字列が、存在しない場合は、空の文字列(ゼロストリング)が返されます。
366         *
367         * @param       cnt             Nodeの階層
368         * @return      ノードリストの文字列(BODY部に書かれた文字列)
369         */
370        public String getText( final int cnt ) {
371                StringBuilder buf = new StringBuilder();
372
373                if( nodeType == OGNodeType.Text ) {
374                        buf.append( text );
375                }
376                else {
377                        for( OGNode node : nodes ) {
378                                buf.append( node.getText( cnt ) );
379                        }
380                }
381
382                String rtn = buf.toString();
383                switch( nodeType ) {
384                        case Comment:   rtn = "<!-- "      + rtn + " -->"; break;
385                        case Cdata:             rtn = "<![CDATA[ " + rtn + " ]]>"; break;
386        //              case Document:
387        //              case Text:
388        //              case DTD:
389        //              case List:
390                        default:                break;
391                }
392
393                return rtn ;
394        }
395
396        /**
397         * オブジェクトの文字列表現を返します。
398         *
399         * 文字列は、OGNodeType により異なります。
400         * Comment ノードの場合は、コメント記号を、Cdata ノードの場合は、CDATA を
401         * つけて出力します。
402         *
403         * @return      このオブジェクトの文字列表現
404         * @see Object#toString()
405         */
406        @Override
407        public String toString() {
408                return getText( -10 );
409        }
410}