    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}