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 org.xml.sax.Attributes; 020 021/** 022 * エレメントをあらわす、OGElement クラスを定義します。 023 * 024 * エレメントは、OGNode クラスを継承し、名称、属性、ノードリストを持つオブジェクトです。 025 * 通常で言うところの、タグになります。 026 * 属性は、OGAttributes クラスで管理します。ノードリスト に関する操作は、OGNodeクラスの実装です。 027 * 028 * OGNode は、enum OGNodeType で区別される状態を持っています。 029 * OGNodeType は、それぞれ、再設定が可能です。 030 * 例えば、既存のエレメントやノードに対して、コメントタイプ(Comment)を指定すると、 031 * ファイル等への出力時にコメントとして出力されます。 032 * 033 * @og.rev 5.1.8.0 (2010/07/01) 新規作成 034 * 035 * @version 5.0 036 * @author Kazuhiko Hasegawa 037 * @since JDK6.0, 038 */ 039public class OGElement extends OGNode { 040 041 private final String qName ; // このタグの名前(nameSpace も含むエレメントの名前) 042 private OGAttributes attri ; // 属性オブジェクト 043 044 // 階層に応じたスペースの設定 045 private static final int PARA_LEN = 8; 046 private static final String PARA_CHAR = "\t"; 047 private static final String[] PARA = new String[PARA_LEN]; 048 static { 049 PARA[0] = CR; 050 final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE ); 051 buf.append( CR ); 052 for( int i=1; i<PARA_LEN; i++ ) { 053 buf.append( PARA_CHAR ); 054 PARA[i] = buf.toString(); 055 } 056 } 057 058 /** 059 * ノード名を指定してのトコンストラクター 060 * 061 * ノード名のみ指定するため、属性と、ノードリストが空のエレメントを構築します。 062 * 063 * @param qName ノード名 064 */ 065 public OGElement( final String qName ) { 066 this( qName,null ); 067 } 068 069 /** 070 * ノード名、属性タブ、属性リストを指定してのトコンストラクター 071 * 072 * 注意 属性値の正規化は必ず行われます。 073 * 属性値に含まれるCR(復帰), LF(改行), TAB(タブ)は、 半角スペースに置き換えられます。 074 * XMLの規定では、属性の並び順は保障されませんが、SAXのAttributesは、XMLに記述された順番で 075 * 取得できていますので、このクラスでの属性リストも、記述順での並び順になります。 076 * 077 * @og.rev 5.2.1.0 (2010/10/01) タグ属性の改行処理を、Set からString[] に変更。 078 * @og.rev 5.6.1.2 (2013/02/22) CR_SET を配列から文字列に変更 079 * 080 * @param qName ノード名 081 * @param atts 属性リスト 082 */ 083 public OGElement( final String qName , final Attributes atts ) { 084 super(); 085 setNodeType( OGNodeType.Element ); 086 087 if( qName == null ) { 088 final String errMsg = "エレメントには、ノード名は必須です。"; 089 throw new OgRuntimeException( errMsg ); 090 } 091 092 this.qName = qName; 093 this.attri = new OGAttributes( atts ) ; 094 } 095 096 /** 097 * ノード名を返します。 098 * 099 * @return ノード名 100 */ 101 public String getTagName() { 102 return qName; 103 } 104 105 /** 106 * 属性オブジェクトを返します。 107 * 108 * これは、org.xml.sax.Attributes ではなく、OGAttributes オブジェクトを返します。 109 * 内部オブジェクトそのものを返しますので、この OGAttributes の変更は、この 110 * エレメントが持つ内部属性も変更されます。 111 * 112 * @return 属性オブジェクト 113 */ 114 public OGAttributes getOGAttributes() { 115 return attri; 116 } 117 118 /** 119 * 属性オブジェクトをセットします。 120 * 121 * 属性オブジェクトのセットは、このメソッドからのみできるようにします。 122 * 内部オブジェクトそのものにセットしますので、異なる OGAttributes をセットしたい場合は、 123 * 外部で、コピーしてからセットしてください。 124 * 125 * @og.rev 5.6.1.2 (2013/02/22) 新規追加 126 * 127 * @param attri 属性オブジェクト(org.opengion.fukurou.xml.OGAttributes) 128 */ 129 public void setOGAttributes( final OGAttributes attri ) { 130 this.attri = attri; 131 } 132 133 /** 134 * 属性リストから、id属性の、属性値を取得します。 135 * 136 * id属性 は、内部的にキャッシュしており、すぐに取り出せます。 137 * タグを特定する場合、一般属性のキーと値で選別するのではなく、 138 * id属性を付与して選別するようにすれば、高速に見つけることが可能になります。 139 * 140 * @og.rev 5.1.9.0 (2010/08/01) 新規追加 141 * 142 * @return id属性値 143 */ 144 public String getId() { 145 // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..; 146 return attri == null ? null : attri.getId() ; 147 } 148 149 /** 150 * 属性リストから、指定の属性キーの、属性値を取得します。 151 * 152 * この処理は、属性リストをすべてスキャンして、キーにマッチする 153 * 属性オブジェクトを見つけ、そこから、属性値を取り出すので、 154 * パフォーマンスに問題があります。 155 * 基本的には、アドレス指定で、属性値を取り出すようにしてください。 156 * 157 * @og.rev 5.6.1.2 (2013/02/22) 新規追加 158 * 159 * @param key 属性キー 160 * 161 * @return 属性値 162 */ 163 public String getVal( final String key ) { 164 // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..; 165 return attri == null ? null : attri.getVal( key ) ; 166 } 167 168 /** 169 * 属性リストに、属性(キー、値のセット)を設定します。 170 * 171 * 属性リストの一番最後に、属性(キー、値のセット)を設定します。 172 * 173 * @param key 属性リストのキー 174 * @param val 属性リストの値 175 */ 176 public void addAttr( final String key , final String val ) { 177 if( attri == null ) { attri = new OGAttributes() ; } 178 attri.add( key,val ) ; 179 } 180 181 /** 182 * 自分自身の状態が、指定の条件に合致しているかどうか、判定します。 183 * 184 * 合致している場合は、true を、合致していない場合は、false を返します。 185 * 186 * 指定の属性が null の場合は、すべてに合致すると判断します。 187 * 例えば、kye のみ指定すると、その属性名を持っているエレメントすべてで 188 * true が返されます。 189 * 実行速度を考えると、ノード名は指定すべきです。 190 * 191 * @param name ノード名 null の場合は、すべての ノード名 に合致 192 * @param key 属性名 null の場合は、すべての 属性名 に合致 193 * @param val 属性値 null の場合は、すべての 属性値 に合致 194 * 195 * @return 条件がこのエレメントに合致した場合 true 196 */ 197 public boolean match( final String name , final String key , final String val ) { 198 // name が存在するが、不一致の場合は、false 199 if( name != null && ! name.equals( qName ) ) { return false; } 200 201 // attri が null なのに、key か val が、null でない場合は合致しないので、false と判断 202 if( attri == null && ( key != null || val != null ) ) { return false; } 203 204 // キーが存在し、値も存在する場合は、その値の合致と同じ結果となる。 205 if( key != null ) { 206 // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..; 207 return val == null 208 ? attri.getAdrs( key ) >= 0 // 値がなければ、存在チェック 209 : val.equals( attri.getVal( key ) ) ; // 値があれば、比較する。 210 211 } 212 213 // 値が存在する場合は、その値が含まれるかチェックし、あれば、true, なければ false 214 if( val != null ) { 215 boolean flag = false; 216 final int len = attri.size(); 217 for( int i=0; i<len; i++ ) { 218 if( val.equals( attri.getVal(i) ) ) { flag = true; break; } 219 } 220 return flag; 221 } 222 223 // 上記の条件以外は、すべてが null なので、true 224 return true; 225 } 226 227 /** 228 * 段落文字列を返します。 229 * 230 * 段落文字列は、階層を表す文字列です。 231 * 通常は TAB ですが、XMLの階層が、PARA_LEN を超えても、段落を増やしません。 232 * 段落の最初の文字は、改行です。 233 * 234 * @og.rev 5.6.1.2 (2013/02/22) 内部テキストがない場合のタグの終了時にスペースは入れない。 235 * @og.rev 5.6.4.4 (2013/05/31) PARA_LEN を超えても、段落を増やしません。 236 * 237 * @param cnt 階層(-1:なし。 238 * @return 段落文字列 239 * @og.rtnNotNull 240 * @see OGNodeType 241 */ 242 private String getPara( final int cnt ) { 243 // 6.4.1.1 (2016/01/16) PMD refactoring. A method should have only one exit point, and that should be the last statement in the method 244 return cnt < 0 245 ? "" 246 : cnt < PARA_LEN 247 ? PARA[cnt] 248 : PARA[PARA_LEN-1] ; // 5.6.4.4 (2013/05/31) PARA_LEN を超えても、段落を増やしません。 249 250 } 251 252 /** 253 * オブジェクトの文字列表現を返します。 254 * 255 * 文字列は、OGNodeType により異なります。 256 * Comment ノードの場合は、コメント記号を、Cdata ノードの場合は、CDATA を 257 * つけて出力します。 258 * 259 * @og.rev 5.6.1.2 (2013/02/22) 内部テキストがない場合のタグの終了時にスペースは入れない。 260 * @og.rev 5.6.4.4 (2013/05/31) 改行3つを改行2つに置換します。 261 * 262 * @param cnt Nodeの階層(-1:なし、0:改行のみ、1:改行+" "・・・・) 263 * @return このオブジェクトの文字列表現 264 * @og.rtnNotNull 265 * @see OGNode#toString() 266 */ 267 @Override 268 public String getText( final int cnt ) { 269 270 // 6.0.2.5 (2014/10/31) char を append する。 271 final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE ) 272 .append( getPara(cnt) ) 273 .append( '<' ).append( qName ) 274 .append( attri.getText( getPara(cnt+1) ) ); 275 276 final String text = super.getText(cnt+1); 277 278 if( text.trim().isEmpty() ) { 279 buf.append( "/>" ); // 5.6.1.2 (2013/02/22) タグの終了時にスペースは入れない。 280 } 281 else { 282 buf.append( '>' ).append( text ) 283 .append( getPara(cnt) ) 284 .append( "</" ).append( qName ).append( '>' ); 285 // .append( CR ); 286 } 287 288 // 6.4.2.1 (2016/02/05) PMD refactoring. Prefer StringBuffer over += for concatenating strings 289 switch( getNodeType() ) { 290 case Comment: buf.insert( 0,"<!-- " ).append( "-->" ); break; 291 case Cdata: buf.insert( 0,"<![CDATA[ " ).append( " ]]>" ); break; 292 // case Text: 293 // case List: 294 default: break; 295 } 296 297 return buf.toString().replaceAll( CR+CR+CR , CR+CR ) ; // 改行3つを改行2つに置換します。 298 299 } 300}