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}