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 */ 016 package org.opengion.fukurou.xml; 017 018 import java.io.ByteArrayInputStream; 019 import java.io.IOException; 020 import java.io.InputStream; 021 import java.io.UnsupportedEncodingException; 022 import java.util.ArrayList; 023 import java.util.HashMap; 024 import java.util.List; 025 import java.util.Locale; 026 import java.util.Map; 027 028 import javax.xml.parsers.ParserConfigurationException; 029 import javax.xml.parsers.SAXParser; 030 import javax.xml.parsers.SAXParserFactory; 031 032 import org.opengion.fukurou.util.StringUtil; 033 import org.xml.sax.Attributes; 034 import org.xml.sax.SAXException; 035 import org.xml.sax.helpers.DefaultHandler; 036 037 /** 038 * XML2TableParser は、XMLを表形式に変換するためのXMLパ?サーです? 039 * XMLのパ?スには、SAXを採用して?す? 040 * 041 * こ?クラスでは、XML??タを?解し?2次??列?表??タ、及び、指定されたキーに対応す? 042 * 属???タのマップを生?します? 043 * 044 * これら?配?を生成するためには、以下?パラメータを指定する?があります? 045 * 046 * ?次??列データ(表??タ)の取り出? 047 * 行?キー(タグ?と??目のキー?(タグ?を指定することで、表??タを取り?します? 048 * 具体的には、行キーのタグセ???とみなし?そ?中に含まれる?キーをその列?"値"と 049 * して?されます?(行キーがN回?現すれば、N行が生?されます?) 050 * もし、行キーの外で??目キーのタグが?現した場合?そ??キーのタグは無視されます? 051 * 052 * また?colKeysにPARENT_TAG、PARENT_FULL_TAGを指定することで、rowKeyで?されたタグの 053 * 直近?親タグ、及びフルの親タグ?親タグの階層?>[タグA]>[タグB]>[タグC]>"で表現)? 054 * 取得することができます? 055 * 056 * 行キー及??キーは、{@link #setTableCols(String, String[])}で?します? 057 * 058 * ②属???タのマップ?取り出? 059 * 属?キー(タグ?を指定することで、そのタグ名に対応した?を???として生?します? 060 * 同じタグ名が?回にわたって出現した場合?値はアペンドされます? 061 * 062 * 属?キーは、{@link #setReturnCols(String[])}で?します? 063 * 064 * ※それぞれのキー??、大??小文字を区別した形で?することができます? 065 * ?、XMLのタグ名とマッチングする際?、大??小文字?区別せずにマッチングされます? 066 * 067 * @version 4.0 068 * @author Hiroki Nakamura 069 * @since JDK5.0, 070 */ 071 public class XML2TableParser extends DefaultHandler { 072 073 /*----------------------------------------------------------- 074 * 表形式パース 075 *-----------------------------------------------------------*/ 076 // 表形式パースの変数 077 String rowCpKey = ""; 078 String colCpKeys = ""; 079 Map<String,Integer> colCpIdxs = new HashMap<String, Integer>(); 080 081 // 表形式?力データ 082 List<String[]> rows = new ArrayList<String[]>(); 083 String[] data = null; 084 String[] cols = null; 085 086 /*----------------------------------------------------------- 087 * Map型パース 088 *-----------------------------------------------------------*/ 089 // Map型パースの変数 090 String rtnCpKeys = ""; 091 092 // Map型?力データ 093 Map<String,String> rtnKeyMap = new HashMap<String, String>(); 094 Map<String,String> rtnMap = new HashMap<String, String>(); 095 096 /*----------------------------------------------------------- 097 * パ?ス中のタグの状態定義 098 *-----------------------------------------------------------*/ 099 boolean isInRow = false; // rowKey中に入る間のみtrue 100 String curQName = ""; // パ?ス中のタグ? ( [タグC] ) 101 String curFQName = ""; // パ?ス中のフルタグ? [タグA]>[タグB]>[タグC] ) 102 103 // 5.1.6.0 (2010/05/01) rowKeyの親タグが取得できるように対? 104 private static final String PARENT_FULL_TAG_KEY = "PARENT_FULL_TAG"; 105 private static final String PARENT_TAG_KEY = "PARENT_TAG"; 106 107 int pFullTagIdx = -1; 108 int pTagIdx = -1; 109 110 /*----------------------------------------------------------- 111 * href、IDによる??タリンク対? 112 *-----------------------------------------------------------*/ 113 String curId = ""; 114 List<RowColId> idList = new ArrayList<RowColId>(); // row,colとそ?IDを記録 115 Map<String,String> idMap = new HashMap<String,String>(); // col__idをキーに値のマップを保持 116 117 final InputStream input; 118 119 /** 120 * XMLの??を指定してパ?サーを形成します? 121 * 122 * @param st XML??タ(??) 123 */ 124 public XML2TableParser( final String st ) { 125 byte[] bts = null; 126 try { 127 bts = st.getBytes( "UTF-8" ); 128 } 129 catch( UnsupportedEncodingException ex ) { 130 // throw new RuntimeException( "不正なエンコードが?されました? ); 131 String errMsg = "不正なエンコードが?されました。エンコー?[UTF-8]" ; 132 throw new RuntimeException( errMsg , ex ); 133 } 134 // XML宣??前に不要な??タがあれ?、取り除きます? 135 int offset = st.indexOf( '<' ); 136 input = new ByteArrayInputStream( bts, offset, bts.length - offset ); 137 } 138 139 /** 140 * ストリー??してパ?サーを形成します? 141 * 142 * @param is XML??タ(ストリー? 143 */ 144 public XML2TableParser( final InputStream is ) { 145 input = is; 146 } 147 148 /** 149 * 2次??列データ(表??タ)の取り出しを行うための行キーと?キーを指定します? 150 * 151 * @og.rev 5.1.6.0 (2010/05/01) rowKeyの親タグが取得できるように対? 152 * @og.rev 5.1.9.0 (2010/08/01) 可変オブジェクトへの参?の直接セ?をコピ?に変更 153 * 154 * @param rKey 行キー 155 * @param cKeys ?キー 156 */ 157 public void setTableCols( final String rKey, final String[] cKeys ) { 158 if( rKey == null || rKey.length() == 0 || cKeys == null || cKeys.length == 0 ) { 159 return; 160 } 161 cols = cKeys.clone(); // 5.1.9.0 (2010/08/01) 162 rowCpKey = rKey.toUpperCase( Locale.JAPAN ); 163 colCpKeys = "," + StringUtil.array2csv( cKeys ).toUpperCase( Locale.JAPAN ) + ","; 164 165 for( int i = 0; i < cols.length; i++ ) { 166 String tmpKey = cols[i].toUpperCase( Locale.JAPAN ); 167 // 5.1.6.0 (2010/05/01) rowKeyの親タグが取得できるように対? 168 if( PARENT_TAG_KEY.equals( tmpKey ) ) { 169 pTagIdx = Integer.valueOf( i ); 170 } 171 else if( PARENT_FULL_TAG_KEY.equals( tmpKey ) ) { 172 pFullTagIdx = Integer.valueOf( i ); 173 } 174 colCpIdxs.put( tmpKey, Integer.valueOf( i ) ); 175 } 176 } 177 178 /** 179 * 属???タのマップ?取り出しを行うための属?キーを指定します? 180 * 181 * @param rKeys 属?キー 182 */ 183 public void setReturnCols( final String[] rKeys ) { 184 if( rKeys == null || rKeys.length == 0 ) { 185 return; 186 } 187 188 rtnCpKeys = "," + StringUtil.array2csv( rKeys ).toUpperCase( Locale.JAPAN ) + ","; 189 for( int i = 0; i < rKeys.length; i++ ) { 190 rtnKeyMap.put( rKeys[i].toUpperCase( Locale.JAPAN ), rKeys[i] ); 191 } 192 } 193 194 /** 195 * 表??タのヘッ??の?名を配?で返します? 196 * 197 * @og.rev 5.1.9.0 (2010/08/01) 可変オブジェクト?参?返しをコピ?返しに変更 198 * 199 * @return 表??タのヘッ??の?名?配? 200 */ 201 public String[] getCols() { 202 // return cols; 203 return (cols == null) ? null : cols.clone(); // 5.1.9.0 (2010/08/01) 204 } 205 206 /** 207 * 表??タ?次??列で返します? 208 * 209 * @return 表??タの2次??? 210 */ 211 public String[][] getData() { 212 // for( String[] dt : rows ) { 213 // System.out.println( "-----------------------" ); 214 // for( int i = 0; i < dt.length; i++ ) { 215 // System.out.println( "col:" + cols[i] + "=" + dt[i] ); 216 // } 217 // } 218 // return rows.toArray( new String[0][0] ); 219 return rows.toArray( new String[rows.size()][0] ); 220 } 221 222 /** 223 * 属???タを???形式で返します? 224 * 225 * @return 属???タのマッ? 226 */ 227 public Map<String,String> getRtn() { 228 // for( Map.Entry<String, String> entry : rtnMap.entrySet() ) { 229 // System.out.println( "param:" + entry.getKey() + "=" + entry.getValue() ); 230 // } 231 return rtnMap; 232 } 233 234 /** 235 * XMLのパ?スを実行します? 236 */ 237 public void parse() { 238 SAXParserFactory spfactory = SAXParserFactory.newInstance(); 239 try { 240 SAXParser parser = spfactory.newSAXParser(); 241 parser.parse( input, this ); 242 } 243 catch( ParserConfigurationException ex ) { 244 throw new RuntimeException( "パ?サーの設定に問題があります?", ex ); 245 } 246 catch( SAXException ex ) { 247 throw new RuntimeException( "パ?スに失敗しました?, ex ); 248 } 249 catch( IOException ex ) { 250 throw new RuntimeException( "??タの読み取りに失敗しました?, ex ); 251 } 252 } 253 254 /** 255 * ドキュメント開始時に行う処?定義します? 256 * (ここでは何もしません) 257 */ 258 // public void startDocument() { 259 // } 260 261 /** 262 * 要??開始タグ読み込み時に行う処?定義します? 263 * 264 * @og.rev 5.1.6.0 (2010/05/01) rowKeyの親タグが取得できるように対? 265 * 266 * @param uri 名前空間U??。要?名前空????を持たな??合?また?名前空間??行われな??合?空?? 267 * @param localName 接頭辞を含まな?ーカル名?名前空間??行われな??合?空?? 268 * @param qName 接頭辞を持つ修飾名?修飾名を使用できな??合?空?? 269 * @param attributes 要?付加された属?。属?が存在しな??合?空の Attributesオブジェク? 270 */ 271 @Override 272 public void startElement( final String uri, final String localName, final String qName, final Attributes attributes ) { 273 274 // 処?のタグ名を設定します? 275 curQName = getCpTagName( qName ); 276 277 if( rowCpKey.equals( curQName ) ) { 278 isInRow = true; 279 data = new String[cols.length]; 280 // 5.1.6.0 (2010/05/01) rowKeyの親タグが取得できるように対? 281 if( pTagIdx >= 0 ) { data[pTagIdx] = getCpParentTagName( curFQName ); } 282 if( pFullTagIdx >= 0 ) { data[pFullTagIdx] = curFQName; } 283 } 284 285 curFQName += ">" + curQName + ">"; 286 287 // href属?で、ID??初め?#")の場合?、その列番号、行番号、IDを記?しておきます?(後で置き換? 288 String href = attributes.getValue( "href" ); 289 if( href != null && href.length() > 0 && href.charAt( 0 ) == '#' ) { 290 int colIdx = -1; 291 if( isInRow && ( colIdx = getColIdx( curQName ) ) >= 0 ) { 292 idList.add( new RowColId( rows.size(), colIdx, href.substring( 1 ) ) ); 293 } 294 } 295 296 // id属?を記?します? 297 curId = attributes.getValue( "id" ); 298 } 299 300 /** 301 * href属?を記?するための簡易?イントクラスです? 302 */ 303 private static class RowColId { 304 final int row; 305 final int col; 306 final String id; 307 308 RowColId( final int rw, final int cl, final String st ) { 309 row = rw; col = cl; id = st; 310 } 311 } 312 313 /** 314 * ?ストデータ読み込み時に行う処?定義します? 315 * 316 * @param ch ?データ配? 317 * @param offset ??列?の開始位置 318 * @param length ??列から使用される文字数 319 */ 320 @Override 321 public void characters( final char[] ch, final int offset, final int length ) { 322 String val = new String( ch, offset, length ); 323 int colIdx = -1; 324 325 // 表形式データの値をセ?します? 326 if( isInRow && ( colIdx = getColIdx( curQName ) ) >= 0 ) { 327 data[colIdx] = ( data[colIdx] == null ? "" : data[colIdx] ) + val; 328 } 329 330 // 属?マップ?値を設定します? 331 // 5.1.6.0 (2010/05/01) 332 if( curQName != null && curQName.length() > 0 && rtnCpKeys.indexOf( curQName ) >= 0 ) { 333 String key = rtnKeyMap.get( curQName ); 334 String curVal = rtnMap.get( key ); 335 rtnMap.put( key, ( curVal == null ? "" : curVal ) + val ); 336 } 337 338 // ID属?が付加された要??値を取り?し?保存します? 339 if( curId != null && curId.length() > 0 && ( colIdx = getColIdx( curQName ) ) >= 0 ) { 340 String curVal = rtnMap.get( colIdx + "__" + curId ); 341 idMap.put( colIdx + "__" + curId, ( curVal == null ? "" : curVal ) + val ); 342 } 343 } 344 345 /** 346 * 要??終?グ読み込み時に行う処?定義します? 347 * 348 * @param uri 名前空?URI。要?名前空?URI を持たな??合?また?名前空間??行われな??合?空?? 349 * @param localName 接頭辞を含まな?ーカル名?名前空間??行われな??合?空?? 350 * @param qName 接頭辞を持つ修飾名?修飾名を使用できな??合?空?? 351 */ 352 @Override 353 public void endElement( final String uri, final String localName, final String qName ) { 354 curQName = ""; 355 curId = ""; 356 357 // 表形式?行データを書き?します? 358 String tmpCpQName = getCpTagName( qName ); 359 if( rowCpKey.equals( tmpCpQName ) ) { 360 rows.add( data ); 361 isInRow = false; 362 } 363 364 curFQName = curFQName.replace( ">" + tmpCpQName + ">", "" ); 365 } 366 367 /** 368 * ドキュメント終?に行う処?定義します? 369 * 370 */ 371 @Override 372 public void endDocument() { 373 // hrefのIDに対応する?を置き換えます? 374 for( RowColId rci : idList ) { 375 rows.get( rci.row )[rci.col] = idMap.get( rci.col + "__" + rci.id ); 376 } 377 } 378 379 /** 380 * PREFIXを取り除き?さらに大?かしたタグ名を返します? 381 * 382 * @param qName PREFIX付きタグ? 383 * 384 * @return PREFIXを取り除?大??タグ? 385 */ 386 private String getCpTagName( final String qName ) { 387 String tmpCpName = qName.toUpperCase( Locale.JAPAN ); 388 int preIdx = -1; 389 // if( ( preIdx = tmpCpName.indexOf( ":" ) ) >= 0 ) { 390 if( ( preIdx = tmpCpName.indexOf( ':' ) ) >= 0 ) { 391 tmpCpName = tmpCpName.substring( preIdx + 1 ); 392 } 393 return tmpCpName; 394 } 395 396 /** 397 * >[タグC]>[タグB]>[タグA]>と?形式?フルタグ名から[タグA](直近?親タグ?? 398 * 取り出します? 399 * 400 * @og.rev 5.1.9.0 (2010/08/01) 引数がメソ??で使用されて?かったため?修正します? 401 * 402 * @param fQName フルタグ? 403 * 404 * @return 親タグ? 405 */ 406 private String getCpParentTagName( final String fQName ) { 407 String tmpPQName = ""; 408 // int curNStrIdx = curFQName.lastIndexOf( ">", curFQName.length() - 2 ) + 1; 409 // int curNEndIdx = curFQName.length() - 1; 410 // if( curNStrIdx >= 0 && curNEndIdx >= 0 && curNStrIdx < curNEndIdx ) { 411 // tmpPQName = curFQName.substring( curNStrIdx, curNEndIdx ); 412 // } 413 int curNStrIdx = fQName.lastIndexOf( ">", fQName.length() - 2 ) + 1; 414 int curNEndIdx = fQName.length() - 1; 415 if( curNStrIdx >= 0 && curNEndIdx >= 0 && curNStrIdx < curNEndIdx ) { 416 tmpPQName = fQName.substring( curNStrIdx, curNEndIdx ); 417 } 418 return tmpPQName; 419 } 420 421 /** 422 * タグ名に相当するカラ??配?番号を返します? 423 * 424 * @og.rev 5.1.6.0 (2010/05/01) colKeysで?できな??目が存在しな??合にエラーとなるバグを修正 425 * 426 * @param tagName タグ? 427 * 428 * @return 配?番号(存在しな??合??1) 429 */ 430 private int getColIdx( final String tagName ) { 431 int idx = -1; 432 if( tagName != null && tagName.length() > 0 && colCpKeys.indexOf( tagName ) >= 0 ) { 433 // 5.1.6.0 (2010/05/01) 434 Integer key = colCpIdxs.get( tagName ); 435 if( key != null ) { 436 idx = key.intValue(); 437 } 438 // idx = colCpIdxs.get( tagName ).intValue(); 439 } 440 return idx; 441 } 442 }