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