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