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