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 }