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