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; 020import org.xml.sax.Attributes; 021 022import static org.opengion.fukurou.system.HybsConst.CR; // 6.1.0.0 (2014/12/26) refactoring 023import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE; // 6.1.0.0 (2014/12/26) refactoring 024 025/** 026 * 属性リストをあらわす、OGAttributes クラスを定義します。 027 * 028 * 属性リストは、キーと値のペアを、並び順で管理しているリストを保持しています。 029 * 内部的には、 org.xml.sax.Attributes からの値の設定と、タブの属性の整列を行うための 030 * 属性タブ、属性の改行の制御、属性の長さの制御 などを行います。 031 * 032 * @og.rev 5.1.8.0 (2010/07/01) 新規作成 033 * 034 * @version 5.0 035 * @author Kazuhiko Hasegawa 036 * @since JDK6.0, 037 */ 038public class OGAttributes { 039 040 /** 属性の個数制限。この個数で改行を行う。 {@value} */ 041 public static final int CR_CNT = 4; 042 /** 属性の長さ制限。これ以上の場合は、改行を行う。 {@value} */ 043 public static final int CR_LEN = 80; 044 045 private final List<OGAtts> attList = new ArrayList<>(); 046 047 private boolean useCR ; // 属性の改行出力を行うかどうか。個数制限が1と同じ 048 private int maxValLen ; // 属性の名前の最大文字数 049 private String id ; // 特別な属性。id で検索を高速化するため。 050 051 /** 052 * デフォルトトコンストラクター 053 * 054 * 取りあえず、属性オブジェクトを構築する場合に使用します。 055 * 属性タブは、改行+タブ、属性リストは、空のリスト、属性改行は、false を初期設定します。 056 * 057 */ 058 public OGAttributes() { 059 // Document empty method チェック対策 060 } 061 062 /** 063 * 属性タブ、属性リスト、属性改行の有無を指定してのトコンストラクター 064 * 065 * 属性タブ、属性リストに null を指定すると、デフォルトトコンストラクターの設定と 066 * 同じ状態になります。 067 * 068 * 注意 属性値の正規化は必ず行われます。 069 * 属性値に含まれるCR(復帰), LF(改行), TAB(タブ)は、半角スペースに置き換えられます。 070 * XMLの規定では、属性の並び順は保障されませんが、SAXのAttributesは、XMLに記述された順番で 071 * 取得できていますので、このクラスでの属性リストも、記述順での並び順になります。 072 * 073 * @og.rev 5.1.9.0 (2010/08/01) id 属性のみ特別にキャッシュしておく。 074 * 075 * @param attri 属性リスト 076 */ 077 public OGAttributes( final Attributes attri ) { 078 079 final int num = attri == null ? 0 : attri.getLength(); 080 int maxLen = 0; 081 for( int i=0; i<num; i++ ) { 082 final String key = attri.getQName(i); 083 final String val = attri.getValue(i); 084 final OGAtts atts = new OGAtts( key,val ); 085 attList.add( atts ); 086 maxLen = atts.maxKeyLen( maxLen ); 087 088 if( "id".equals( key ) ) { id = val; } // 5.1.9.0 (2010/08/01) 089 } 090 091 maxValLen = maxLen; 092 } 093 094 /** 095 * 属性改行の有無を設定します。 096 * 097 * タグによって、属性が多くなったり、意味が重要な場合は、属性1つづつに改行を 098 * 行いたいケースがあります。 099 * 属性改行をtrue に設定すると、属性一つづつで、改行を行います。 100 * 101 * false の場合は、自動的な改行処理が行われます。 102 * これは、属性の個数制限(CR_CNT)を超える場合は、改行を行います。 103 * 104 * @param useCR 属性改行の有無(true:1属性単位の改行) 105 * @see #CR_CNT 106 * @see #CR_LEN 107 */ 108 public void setUseCR( final boolean useCR ) { 109 this.useCR = useCR; 110 } 111 112 /** 113 * 属性リストの個数を取得します。 114 * 115 * @return 属性リストの個数 116 */ 117 public int size() { 118 return attList.size(); 119 } 120 121 /** 122 * 属性リストから、指定の配列番号の、属性キーを取得します。 123 * 124 * @param adrs 配列番号 125 * 126 * @return 属性キー 127 */ 128 public String getKey( final int adrs ) { 129 return attList.get(adrs).KEY ; 130 } 131 132 /** 133 * 属性リストから、指定の配列番号の、属性値を取得します。 134 * 135 * @param adrs 配列番号 136 * 137 * @return 属性値 138 */ 139 public String getVal( final int adrs ) { 140 return attList.get(adrs).VAL ; 141 } 142 143 /** 144 * 属性リストから、指定の属性キーの、属性値を取得します。 145 * 146 * この処理は、属性リストをすべてスキャンして、キーにマッチする 147 * 属性オブジェクトを見つけ、そこから、属性値を取り出すので、 148 * パフォーマンスに問題があります。 149 * 基本的には、アドレス指定で、属性値を取り出すようにしてください。 150 * 151 * @og.rev 5.1.9.0 (2010/08/01) 新規追加 152 * 153 * @param key 属性キー 154 * 155 * @return 属性値 156 */ 157 public String getVal( final String key ) { 158 String val = null; 159 160 if( key != null ) { 161 for( final OGAtts atts : attList ) { 162 if( key.equals( atts.KEY ) ) { 163 val = atts.VAL; 164 break; 165 } 166 } 167 } 168 169 return val; 170 } 171 172 /** 173 * 属性リストから、指定の属性キーの、アドレスを取得します。 174 * 175 * どちらかというと、キーの存在チェックに近い処理を行います。 176 * この処理は、属性リストをすべてスキャンして、キーにマッチする 177 * 属性オブジェクトを見つけ、そこから、属性値を取り出すので、 178 * パフォーマンスに問題があります。 179 * 180 * @og.rev 5.1.9.0 (2010/08/01) 新規追加 181 * 182 * @param key 属性キー 183 * 184 * @return アドレス キーが存在しない場合は、-1 を返す。 185 */ 186 public int getAdrs( final String key ) { 187 int adrs = -1; 188 189 if( key != null ) { 190 for( int i=0; i<attList.size(); i++ ) { 191 if( key.equals( attList.get(i).KEY ) ) { 192 adrs = i; 193 break; 194 } 195 } 196 } 197 198 return adrs; 199 } 200 201 /** 202 * 属性リストから、id属性の、属性値を取得します。 203 * 204 * id属性 は、内部的にキャッシュしており、すぐに取り出せます。 205 * タグを特定する場合、一般属性のキーと値で選別するのではなく、 206 * id属性を付与して選別するようにすれば、高速に見つけることが可能になります。 207 * 208 * @og.rev 5.1.9.0 (2010/08/01) 新規追加 209 * 210 * @return id属性値 211 */ 212 public String getId() { return id; } 213 214 /** 215 * 属性リストの、指定の配列番号に、属性値を設定します。 216 * 217 * @og.rev 5.1.9.0 (2010/08/01) id 属性のみ特別にキャッシュしておく。 218 * 219 * @param adrs 配列番号 220 * @param val 属性値 221 */ 222 public void setVal( final int adrs , final String val ) { 223 final OGAtts atts = attList.remove(adrs) ; 224 attList.add( adrs , new OGAtts( atts.KEY,val ) ); 225 226 if( "id".equals( atts.KEY ) ) { id = val; } // 5.1.9.0 (2010/08/01) 227 } 228 229 /** 230 * 属性リストに、指定のキー、属性値を設定します。 231 * もし、属性リストに、指定のキーがあれば、属性値を変更します。 232 * なければ、最後に追加します。 233 * 234 * @og.rev 5.6.1.2 (2013/02/22) 新規追加 235 * 236 * @param key 属性キー 237 * @param val 属性値 238 */ 239 public void setVal( final String key , final String val ) { 240 final int adrs = getAdrs( key ); 241 if( adrs < 0 ) { add( key,val ); } 242 else { setVal( adrs,val ); } 243 } 244 245 /** 246 * 属性リストに、属性(キー、値のセット)を設定します。 247 * 248 * 属性リストの一番最後に、属性(キー、値のセット)を設定します。 249 * 250 * @og.rev 5.1.9.0 (2010/08/01) id 属性のみ特別にキャッシュしておく。 251 * 252 * @param key 属性リストのキー 253 * @param val 属性リストの値 254 */ 255 public void add( final String key , final String val ) { 256 257 final OGAtts atts = new OGAtts( key,val ); 258 attList.add( atts ); 259 maxValLen = atts.maxKeyLen( maxValLen ); 260 261 if( "id".equals( key ) ) { id = val; } // 5.1.9.0 (2010/08/01) 262 } 263 264 /** 265 * 指定のアドレスの属性リストに、属性(キー、値のセット)を設定します。 266 * 267 * 指定のアドレスの属性を置き換えるのではなく追加します。 268 * 269 * @og.rev 5.1.9.0 (2010/08/01) id 属性のみ特別にキャッシュしておく。 270 * 271 * @param adrs 属性リストのアドレス 272 * @param key 属性リストのキー 273 * @param val 属性リストの値 274 */ 275 public void add( final int adrs , final String key , final String val ) { 276 277 final OGAtts atts = new OGAtts( key,val ); 278 attList.add( adrs , atts ); 279 maxValLen = atts.maxKeyLen( maxValLen ); 280 281 if( "id".equals( key ) ) { id = val; } // 5.1.9.0 (2010/08/01) 282 } 283 284 /** 285 * 指定のアドレスの属性リストから、属性情報を削除します。 286 * 287 * 指定のアドレスの属性を置き換えるのではなく追加します。 288 * 289 * @og.rev 5.1.9.0 (2010/08/01) id 属性のみ特別にキャッシュしておく。 290 * 291 * @param adrs 属性リストのアドレス 292 */ 293 public void remove( final int adrs ) { 294 final OGAtts atts = attList.remove(adrs) ; 295 296 if( "id".equals( atts.KEY ) ) { id = null; } // 5.1.9.0 (2010/08/01) 297 298 // 削除したキーが maxValLen だったとしても、再計算は、行いません。 299 // 再計算とは、次の長さを見つける必要があるので、すべての OGAtts をもう一度 300 // チェックする必要が出てくるためです。 301 } 302 303 /** 304 * オブジェクトの文字列表現を返します。 305 * 306 * 属性については、並び順は、登録順が保障されます。 307 * 308 * 属性は、3つのパターンで文字列化を行います。 309 * ・useCR=true の場合 310 * この場合は、属性を1行ずつ改行しながら作成します。属性キーは、 311 * 最大長+1 でスペース埋めされて、整形されます。 312 * ・useCR=false の場合 313 * ・属性の個数制限(CR_CNT)単位に、改行が行われます。 314 * これは、属性が右に多く並びすぎるのを防ぎます。 315 * ・属性の長さ制限(CR_LEN)単位で、改行が行われます。 316 * これは、たとえ、属性の個数が少なくても、文字列として長い場合は、 317 * 改行させます。 318 * 319 * @og.rev 5.6.1.2 (2013/02/22) 改行処理の修正。最後の属性処理の後にも改行を入れる。 320 * @og.rev 5.6.4.4 (2013/05/31) 改行処理の修正。attTabが、ゼロ文字列の場合の対応。 321 * 322 * @param attTab Nodeの階層を表す文字列。 323 * @return このオブジェクトの文字列表現 324 * @og.rtnNotNull 325 * @see OGNode#toString() 326 * @see #setUseCR( boolean ) 327 */ 328 public String getText( final String attTab ) { 329 final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE ); 330 331 final String crTab = attTab.length() > 0 ? attTab : (CR + "\t") ; 332 333 if( useCR ) { 334 for( int i=0; i<size(); i++ ) { 335 final OGAtts atts = attList.get(i); 336 // 8.5.4.2 (2024/01/12) PMD 7.0.0 ConsecutiveAppendsShouldReuse 対応 337 buf.append( crTab ) 338 // 6.0.2.5 (2014/10/31) char を append する。 339 .append( atts.getAlignKey( maxValLen ) ).append( '=' ).append( atts.QRT_VAL ); 340 } 341 // 5.6.1.2 (2013/02/22) 改行処理の修正。最後の属性処理の後にも改行を入れる。 342 buf.append( CR ); 343 } 344 else { 345 int crCnt = 0; 346 int crLen = 0; 347 for( int i=0; i<size(); i++ ) { 348 final OGAtts atts = attList.get(i); 349 crCnt++ ; 350 crLen += atts.LEN; 351 // 6.0.2.5 (2014/10/31) char を append する。 352 if( i>0 && (crCnt > CR_CNT || crLen > CR_LEN) ) { 353 // 8.5.4.2 (2024/01/12) PMD 7.0.0 ConsecutiveAppendsShouldReuse 対応 354 buf.append( crTab ) 355 .append( atts.KEY ).append( '=' ).append( atts.QRT_VAL ); 356 crCnt = 0; 357 crLen = 0; 358 } 359 else { 360 buf.append( ' ' ).append( atts.KEY ).append( '=' ).append( atts.QRT_VAL ); 361 } 362 } 363 } 364 365 return buf.toString(); 366 } 367 368 /** 369 * オブジェクトの文字列表現を返します。 370 * 371 * @og.rev 5.6.1.2 (2013/02/22) 改行処理の修正。最後の属性処理の後にも改行を入れる。 372 * 373 * @return このオブジェクトの文字列表現 374 * @og.rtnNotNull 375 * @see OGNode#toString() 376 */ 377 @Override 378 public String toString() { 379 return getText( " " ); 380 } 381 382 /** 383 * 属性キーと属性値を管理する クラス 384 * 385 * 属性自身は、属性キーと属性値のみで十分ですが、改行処理や文字列の長さ設定で、 386 * 予め内部処理をしておきたいため、クラス化しています。 387 * 388 * 内部変数は、final することで定数化し、アクセスメソッド経由ではなく、直接内部変数を 389 * 参照させることで、見易さを優先しています。 390 * 391 * @og.rev 6.3.9.1 (2015/11/27) private static final class に変更。 392 * 393 */ 394 private static final class OGAtts { 395 /** 属性の長さをそろえるための空白文字の情報 **/ 396 private static final String SPACE = " "; // 5.1.9.0 (2010/09/01) public ⇒ private へ変更 397 398 /** 属性キー **/ 399 private final String KEY ; 400 /** 属性値 **/ 401 private final String VAL ; 402 private final int KLEN ; 403 private final int LEN ; 404 private final String QRT_VAL; 405 406 /** 407 * 引数を指定して構築する、コンストラクター 408 * 409 * 属性キーと、属性値 を指定して、オブジェクトを構築します。 410 * 411 * 412 * @og.rev 6.3.9.1 (2015/11/27) 修飾子を、public → なし(パッケージプライベート)に変更。 413 * @param key 属性キー 414 * @param val 属性値 415 */ 416 /* default */ OGAtts( final String key , final String val ) { 417 KEY = key; 418 VAL = val == null ? "" : htmlFilter(val) ; 419 KLEN = key.length(); 420 LEN = KLEN + VAL.length(); 421 422 QRT_VAL = VAL.indexOf( '"' ) >= 0 ? ("'" + VAL + "'") : ("\"" + VAL + "\"") ; 423 } 424 425 /** 426 * キーの文字長さの比較で、大きい数字を返します。 427 * 428 * 属性キーの最大の文字列長を求めるため、引数の長さと、属性キーの長さを比較して、 429 * 大きな値の方を返します。 430 * この処理を、属性すべてに行えば、最終的に最も大きな値が残ることになります。 431 * 432 * @param maxLen 属性キーの最大長さ 433 * 434 * @return 属性リスト群の長さ補正が行われた、属性キー+空白文字列 435 */ 436 /* default */ int maxKeyLen( final int maxLen ) { 437 return maxLen > KLEN ? maxLen : KLEN ; 438 } 439 440 /** 441 * 長さ補正が行われた属性キーを取得します。 442 * 443 * useCR=true の場合に、属性の改行が行われますが、そのときに、キーが縦に並びます。 444 * そして、値も縦に並ぶため、間の 「=」記号の位置をそろえて、表示します。 445 * 属性リストの最大長さ+1 になるように、キーの文字列にスペースを埋めます。 446 * これにより、属性を改行して表示しても、値の表示位置がそろいます。 447 * 448 * @param maxLen 属性キーの最大長さ 449 * 450 * @return 属性リスト群の長さ補正が行われた、属性キー+空白文字列 451 * @og.rtnNotNull 452 */ 453 /* default */ String getAlignKey( final int maxLen ) { 454 return KEY + SPACE.substring( KLEN,maxLen ) ; 455 } 456 457 /** 458 * HTML上のエスケープ文字を変換します。 459 * 460 * HTMLで表示する場合にきちんとエスケープ文字に変換しておかないと 461 * Script を実行されたり、不要なHTMLコマンドを潜り込まされたりするため、 462 * セキュリティーホールになる可能性があるので、注意してください。 463 * 464 * ※ オリジナルは、org.opengion.fukurou.util.StringUtil#htmlFilter( String ) 465 * ですが、ダブルクオート、シングルクオートの変換処理を省いています。 466 * 467 * @og.rev 8.5.5.1 (2024/02/29) switch文にアロー構文を使用 468 * 469 * @param input HTMLエスケープ前の文字列 470 * 471 * @return エスケープ文字に変換後の文字列 472 * @og.rtnNotNull 473 * @see org.opengion.fukurou.util.StringUtil#htmlFilter( String ) 474 */ 475 private String htmlFilter( final String input ) { 476 if( input == null || input.isEmpty() ) { return ""; } 477 478 final StringBuilder rtn = new StringBuilder( BUFFER_MIDDLE ); 479// char ch; 480 for( int i=0; i<input.length(); i++ ) { 481// ch = input.charAt(i); 482 final char ch = input.charAt(i); 483 // 8.5.5.1 (2024/02/29) switch文にアロー構文を使用 484// switch( ch ) { 485// case '<' : rtn.append("<"); break; 486// case '>' : rtn.append(">"); break; 487// // case '"' : rtn.append("""); break; 488// // case '\'' : rtn.append("'"); break; 489// case '&' : rtn.append("&"); break; 490// default : rtn.append(ch); break; // 6.0.2.5 (2014/10/31) break追記 491// } 492 switch( ch ) { 493 case '<' -> rtn.append("<"); 494 case '>' -> rtn.append(">"); 495 // case '"' -> rtn.append(""") 496 // case '\'' -> rtn.append("'"); 497 case '&' -> rtn.append("&"); 498 default -> rtn.append(ch); // 6.0.2.5 (2014/10/31) break追記 499 } 500 } 501 return rtn.toString() ; 502 } 503 } 504}