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