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}