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