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.opengion.fukurou.util.Closer ;
019import org.opengion.fukurou.util.FileUtil ;
020
021import java.io.PrintWriter ;
022import java.io.BufferedWriter ;
023import java.io.OutputStreamWriter ;
024import java.io.FileOutputStream ;
025import java.io.IOException ;
026import java.io.File;
027import java.io.StringReader ;
028import java.io.FileNotFoundException ;
029import java.io.UnsupportedEncodingException;
030import java.util.Stack;
031import java.util.List;
032import java.util.ArrayList;
033import java.util.Map;
034import java.util.HashMap;
035
036import org.xml.sax.Attributes;
037import org.xml.sax.ext.DefaultHandler2;
038import org.xml.sax.InputSource ;
039import org.xml.sax.SAXException;
040import org.xml.sax.SAXParseException;
041import javax.xml.parsers.SAXParserFactory;
042import javax.xml.parsers.SAXParser;
043import javax.xml.parsers.ParserConfigurationException;
044
045/**
046 * JSP/XMLファイルを読み取って、OGNode/OGElement オブジェクトを取得する、パーサークラスです。
047 *
048 * 自分自身が、DefaultHandler2 を拡張していますので、パーサー本体になります。
049 * javax.xml.parsers および、org.w3c.dom の簡易処理を行います。
050 * read で、トップレベルの OGNode を読み込み、write で、ファイルに書き出します。
051 * 通常の W3C 系の オブジェクトを利用しないのは、属性の並び順を保障するためです。
052 * ただし、属性のタブ、改行は失われます。
053 * また、属性値に含まれるCR(復帰), LF(改行), TAB(タブ)は、 半角スペースに置き換えられます。
054 * これは、SAXParser 側での XML の仕様の関係で、属性は、正規化されるためです。
055 *
056 * @og.rev 5.1.8.0 (2010/07/01) 新規作成
057 * @og.rev 5.1.9.0 (2010/08/01) static メソッドを廃止。通常のオブジェクトクラスとして扱います。
058 *
059 * @version  5.0
060 * @author   Kazuhiko Hasegawa
061 * @since    JDK6.0,
062 */
063public class JspSaxParser extends DefaultHandler2 {
064        public static final String CR = System.getProperty("line.separator");
065
066        private final List<JspParserFilter> filters = new ArrayList<JspParserFilter>(); // 5.1.9.0 (2010/08/01)
067        private SAXParser parser = null;
068
069        // 以下、パース時に使用する変数。(パース毎に初期化する。)
070        private Map<String,OGElement> idMap = null;             // 5.1.9.0 (2010/08/01)
071        private Stack<OGNode>             stack = null;
072
073        private OGNode  ele                     = null;         // 現時点のエレメントノード
074        private String  attTab          = "";           // tagBefore の一次TEMP
075        private boolean inCDATA         = false;        // CDATA エレメントの中かどうかの判定
076        private boolean inEntity        = false;        // Entity の中かどうかの判定
077        private String  filename        = null;         // 処理実行中のファイル名
078
079        /**
080         * XMLファイルを読み込み、OGDocument を返します。
081         *
082         * 内部的には、SAXParserFactory から、SAXParser を構築し、Property に、
083         * http://xml.org/sax/properties/lexical-handler を設定しています。
084         * コメントノードを処理するためです。
085         *
086         * @og.rev 5.1.9.0 (2010/08/01) static からノーマルに変更
087         *
088         * @param       aFile   XMLファイル
089         *
090         * @return      ファイルから読み取って構築したOGDocumentオブジェクト
091         */
092        public OGDocument read( final File aFile ) {
093                filename = aFile.getAbsolutePath() ;
094
095                try {
096                        if( parser == null ) {
097                                // SAXパーサーファクトリを生成
098                                SAXParserFactory spfactory = SAXParserFactory.newInstance();
099
100                                // SAXパーサーを生成
101                                parser = spfactory.newSAXParser();
102
103                                parser.setProperty("http://xml.org/sax/properties/lexical-handler", this);      // LexicalHandler として
104                        }
105                        // XMLファイルを指定されたハンドラーで処理します
106                        parser.parse( aFile, this );
107
108                } catch ( ParserConfigurationException ex ) {
109                        String errMsg = "重大な構成エラーが発生しました。"
110                                        + CR + "\t" + ex.getMessage()
111                                        + CR + "\t" + aFile ;
112                        throw new RuntimeException( errMsg,ex );
113        //      5.1.9.0 (2010/08/01) 廃止
114        //      } catch ( SAXNotRecognizedException ex ) {
115        //              String errMsg = "XMLReader は、認識されない機能またはプロパティー識別子を検出しました。"
116        //                              + CR + "\t" + ex.getMessage()
117        //                              + CR + "\t" + aFile ;
118        //              if( ex2 != null ) { errMsg = errMsg + CR + "\t" + ex2.getMessage(); }
119        //              throw new RuntimeException( errMsg,ex );
120        //      } catch ( SAXNotSupportedException ex ) {
121        //              String errMsg = "XMLReader は、要求された操作 (状態または値の設定) を実行できませんでした。"
122        //                              + CR + "\t" + ex.getMessage()
123        //                              + CR + "\t" + aFile ;
124        //              if( ex2 != null ) { errMsg = errMsg + CR + "\t" + ex2.getMessage(); }
125        //              throw new RuntimeException( errMsg,ex );
126                } catch ( SAXException ex ) {
127                        String errMsg = "SAX の一般的なエラーが発生しました。"
128                                        + CR + "\t" + ex.getMessage()
129                                        + CR + "\t" + aFile ;
130                        Exception ex2 = ex.getException();
131                        if( ex2 != null ) { errMsg = errMsg + CR + "\t" + ex2.getMessage(); }
132                        throw new RuntimeException( errMsg,ex );
133                } catch ( IOException ex ) {
134                        String errMsg = "ファイル読取時にエラーが発生しました。"
135                                        + CR + "\t" + ex.getMessage()
136                                        + CR + "\t" + aFile ;
137                        throw new RuntimeException( errMsg,ex );
138        //      5.1.9.0 (2010/08/01) 廃止
139        //      } catch( RuntimeException ex ) {
140        //              String errMsg = "実行時エラーが発生しました。"
141        //                              + CR + "\t" + ex.getMessage()
142        //                              + CR + "\t" + aFile ;
143        //              throw new RuntimeException( errMsg,ex );
144                }
145
146                return getDocument() ;
147        }
148
149        /**
150         * XML形式で表現された、文字列(String) から、OGDocument を構築します。
151         *
152         * 処理的には、#read( File ) と同じで、取り出す元が、文字列というだけです。
153         * XMLファイルからの読み込みと異なり、通常は、Element を表現した文字列が作成されますが、
154         * 返されるのは、OGDocument オブジェクトです。
155         *
156         * @og.rev 5.1.9.0 (2010/08/01) static からノーマルに変更
157         *
158         * @param       str     XML形式で表現された文字列
159         *
160         * @return      ファイルから読み取って構築した OGDocumentオブジェクト
161         */
162        public OGDocument string2Node( final String str ) {
163                filename = null ;
164
165                try {
166                        if( parser == null ) {
167                                // SAXパーサーファクトリを生成
168                                SAXParserFactory spfactory = SAXParserFactory.newInstance();
169                                // SAXパーサーを生成
170                                parser = spfactory.newSAXParser();
171
172                                parser.setProperty("http://xml.org/sax/properties/lexical-handler", this);      // LexicalHandler として
173                        }
174
175                        // XMLファイルを指定されたデフォルトハンドラーで処理します
176                        InputSource source = new InputSource( new StringReader( str ) );
177                        parser.parse( source, this );
178
179                } catch ( ParserConfigurationException ex ) {
180                        String errMsg = "重大な構成エラーが発生しました。"
181                                        + CR + ex.getMessage();
182                        throw new RuntimeException( errMsg,ex );
183        //      5.1.9.0 (2010/08/01) 廃止
184        //      } catch ( SAXNotRecognizedException ex ) {
185        //              String errMsg = "XMLReader は、認識されない機能またはプロパティー識別子を検出しました。"
186        //                              + CR + ex.getMessage();
187        //              Exception ex2 = ex.getException();
188        //              if( ex2 != null ) { errMsg = errMsg + CR + "\t" + ex2.getMessage(); }
189        //              throw new RuntimeException( errMsg,ex );
190                } catch ( SAXException ex ) {
191                        String errMsg = "SAX の一般的なエラーが発生しました。"
192                                        + CR + ex.getMessage();
193                        Exception ex2 = ex.getException();
194                        if( ex2 != null ) { errMsg = errMsg + CR + "\t" + ex2.getMessage(); }
195                        throw new RuntimeException( errMsg,ex );
196                } catch ( IOException ex ) {
197                        String errMsg = "ストリームオブジェクト作成時にエラーが発生しました。"
198                                        + CR + ex.getMessage();
199                        throw new RuntimeException( errMsg,ex );
200        //      5.1.9.0 (2010/08/01) 廃止
201        //      } catch( RuntimeException ex ) {
202        //              String errMsg = "実行時エラーが発生しました。"
203        //                              + CR + ex.getMessage();
204        //              throw new RuntimeException( errMsg,ex );
205                }
206
207                return getDocument() ;
208        }
209
210        /**
211         * OGDocument を所定のファイルに、XML形式で書き出します。
212         *
213         * @param       aFile   書き出すファイル
214         * @param       node    書き出す OGDocument
215         */
216        public void write( final File aFile, final OGDocument node ) {
217                PrintWriter out    = null;
218                String      encode = node.getEncode();
219                try {
220                        out = new PrintWriter( new BufferedWriter( new OutputStreamWriter( new FileOutputStream(aFile),encode )));
221                        out.println( node.toString() );
222                } catch ( FileNotFoundException ex ) {
223                        String errMsg = "指定されたパス名で示されるファイルが存在しませんでした。"
224                                        + CR + "\t" + ex.getMessage()
225                                        + CR + "\t" + aFile ;
226                        throw new RuntimeException( errMsg,ex );
227                } catch ( UnsupportedEncodingException ex ) {
228                        String errMsg = "文字のエンコーディング(" + encode + ")がサポートされていません。"
229                                        + CR + "\t" + ex.getMessage()
230                                        + CR + "\t" + aFile ;
231                        throw new RuntimeException( errMsg,ex );
232        //      5.1.9.0 (2010/08/01) 廃止
233        //      } catch( RuntimeException ex ) {
234        //              String errMsg = "実行時エラーが発生しました。"
235        //                              + CR + "\t" + ex.getMessage()
236        //                              + CR + "\t" + aFile ;
237        //              throw new RuntimeException( errMsg,ex );
238                }
239                finally {
240                        Closer.ioClose( out );
241                }
242        }
243
244        /**
245         * ディレクトリの再帰処理でパース処理を行います。
246         *
247         * @og.rev 5.1.9.0 (2010/08/01) static からノーマルに変更
248         *
249         * @param       fromFile        読み取りもとのファイル/フォルダ
250         * @param       toFile  書き込み先のファイル/フォルダ
251         */
252        public void copyDirectry( final File fromFile, final File toFile ) {
253                // コピー元がファイルの場合はコピーして、終了する。
254                if( fromFile.exists() && fromFile.isFile() ) {
255                        boolean isOK = false;
256                        String name = fromFile.getName();
257                        if( name.endsWith( ".jsp" ) || name.endsWith( ".xml" ) ) {
258                                try {
259                                        OGDocument doc = read( fromFile );
260                                        if( doc != null && !filters.isEmpty() ) {
261                                                for( JspParserFilter filter: filters ) {
262                                                        doc = filter.filter( doc );
263                                                        if( doc == null ) { break; }    // エラー、または処理の中止
264                                                }
265                                        }
266                                        if( doc != null ) {
267                                                write( toFile,doc );
268                                                isOK = true;
269                                        }
270                                }
271                                catch( RuntimeException ex ) {
272                        //              ex.printStackTrace();
273                                        System.out.println( ex.getMessage() );
274                                }
275                        }
276
277                        // JSPやXMLでない、パースエラー、書き出しエラーなど正常終了できなかった場合は、バイナリコピー
278                        if( !isOK ) {
279                                FileUtil.copy( fromFile,toFile,true );
280                        }
281                        return ;
282                }
283
284                // コピー先ディレクトリが存在しなければ、作成する
285                // 6.0.0.1 (2014/04/25) These nested if statements could be combined
286                if( !toFile.exists() && !toFile.mkdirs() ) {
287                        System.err.println( toFile + " の ディレクトリ作成に失敗しました。" );
288                        return ;
289                }
290
291                // ディレクトリ内のファイルをすべて取得する
292                File[] files = fromFile.listFiles();
293
294                // ディレクトリ内のファイルに対しコピー処理を行う
295                for( int i = 0; i<files.length; i++ ){
296                        copyDirectry( files[i], new File( toFile, files[i].getName()) );
297                }
298        }
299
300        /**
301         * copyDirectry 処理で、OGDocument をフィルター処理するオブジェクトを登録します。
302         *
303         * 内部リストへフィルターを追加します。
304         * フィルター処理は、追加された順に行われます。
305         * 内部リストへの追加はできますが、削除はできません。
306         *
307         * @og.rev 5.1.9.0 (2010/08/01) 新規追加
308         *
309         * @param       filter  フィルターオブジェクト
310         */
311        public void addFilter( final JspParserFilter filter ) {
312                filters.add( filter );
313        }
314
315        /**
316         * サンプルプログラムです。
317         *
318         * 引数の IN がファイルの場合は、OUTもファイルとして扱います。
319         * IN がフォルダの場合は、階層にしたがって、再帰的に処理を行い、OUT に出力します。
320         * フォルダ階層をパースしている最中に、XMLとして処理できない、処理中にエラーが発生した
321         * などの場合は、バイナリコピーを行います。
322         *
323         * "Usage: org.opengion.fukurou.xml.JspSaxParser  &lt;inFile|inDir&gt; &lt;outFile|outDir&gt; [&lt;JspParserFilter1&gt; ・・・ ]"
324         *
325         * @param       args    コマンド引数配列
326         * @throws Exception なんらかのエラーが発生した場合
327         */
328        public static void main( final String[] args ) throws Exception {
329                if( args.length < 2 ) {
330                        System.out.println( "Usage: org.opengion.fukurou.xml.JspSaxParser <inFile|inDir> <outFile|outDir> [<JspParserFilter1> ・・・ ]" );
331                }
332
333                File in   = new File( args[0] );
334                File out  = new File( args[1] );
335
336                JspSaxParser jsp = new JspSaxParser();
337
338                if( args.length >= 3 ) {
339                        for( int i=2; i<args.length; i++ ) {
340                                JspParserFilter filter = (JspParserFilter)Class.forName( args[i] ).newInstance();
341                                jsp.addFilter( filter );
342                        }
343                }
344
345                jsp.copyDirectry( in,out );
346        }
347
348        // ********************************************************************************************** //
349        // **                                                                                          ** //
350        // ** ここから下は、DefaultHandler2 の実装になります。                                         ** //
351        // **                                                                                          ** //
352        // ********************************************************************************************** //
353
354        /**
355         * 文書の開始通知を受け取ります。
356         *
357         * インタフェース ContentHandler 内の startDocument
358         *
359         * @see org.xml.sax.helpers.DefaultHandler#startDocument()
360         * @see org.xml.sax.ContentHandler#startDocument()
361         */
362        @Override
363        public void startDocument() {
364                stack   = new Stack<OGNode>();
365                ele             = new OGDocument();
366                ((OGDocument)ele).setFilename( filename );
367
368                idMap   = new HashMap<String,OGElement>();              // 5.1.9.0 (2010/08/01) 追加
369
370                attTab   = "";          // tagBefore の一次TEMP
371                inCDATA  = false;       // CDATA エレメントの中かどうかの判定
372                inEntity = false;       // Entity の中かどうかの判定
373        }
374
375        /**
376         * 要素の開始通知を受け取ります。
377         *
378         * インタフェース ContentHandler 内の startElement
379         *
380         * @param       uri                     名前空間 URI。要素が名前空間 URI を持たない場合、または名前空間処理が実行されない場合は null
381         * @param       localName       前置修飾子を含まないローカル名。名前空間処理が行われない場合は空文字列
382         * @param       qName           接頭辞を持つ修飾名。修飾名を使用できない場合は空文字列
383         * @param       attributes      要素に付加された属性。属性が存在しない場合、空の Attributesオブジェクト
384         *
385         * @see org.xml.sax.helpers.DefaultHandler#startElement(String,String,String,Attributes)
386         * @see org.xml.sax.ContentHandler#startElement(String,String,String,Attributes)
387         */
388        @Override
389        public void startElement( final String uri, final String localName, final String qName, final Attributes attributes ) {
390                OGElement newEle = new OGElement( qName,attributes );
391                String id = newEle.getId();
392                if( id != null ) { idMap.put( id,newEle ); }            // 5.1.9.0 (2010/08/01) idをMapにキャッシュ
393
394                ele.addNode( newEle );
395                stack.push( ele );
396                ele = newEle ;
397        }
398
399        /**
400         * 要素内の文字データの通知を受け取ります。
401         *
402         * エンティティー内かどうかを判断する、inEntity フラグが true の間は、
403         * 何も処理しません。
404         *
405         * インタフェース ContentHandler 内の characters
406         *
407         * @param       cbuf    文字データ配列
408         * @param       off             文字配列内の開始位置
409         * @param       len             文字配列から使用される文字数
410         *
411         * @see org.xml.sax.helpers.DefaultHandler#characters(char[],int,int)
412         * @see org.xml.sax.ContentHandler#characters(char[],int,int)
413         */
414        @Override
415        public void characters( final char[] cbuf, final int off, final int len ) {
416                if( inEntity ) { return ; }             // &lt; ⇒ < に変換されるので、エンティティ内では、なにも処理しない。
417
418                String text = toText( cbuf,off,len );
419                if( inCDATA ) {
420                        ele.addNode( text );
421                        return ;
422                }
423
424                OGNode node = new OGNode( text );
425                ele.addNode( node );
426
427                // '\r'(CR:復帰)+ '\n'(LF:改行)の可能性があるが、 '\n'(LF:改行)が、より後ろにあるので、これで判定。
428                int lastIdx = text.lastIndexOf( '\n' );
429                if( lastIdx >= 0 ) {
430                        attTab = text.substring( lastIdx+1 );   // 改行から、最後までの部分文字列
431                }
432                else {
433                        attTab = text;                                                  // 改行がないので、すべて
434                }
435        }
436
437        /**
438         * CDATA セクションの開始を報告します。
439         *
440         * CDATA セクションのコンテンツは、正規の characters イベントを介して報告されます。
441         * このイベントは境界の報告だけに使用されます。
442         *
443         * インタフェース LexicalHandler 内の startCDATA
444         *
445         * @see org.xml.sax.ext.DefaultHandler2#startCDATA()
446         * @see org.xml.sax.ext.LexicalHandler#startCDATA()
447         */
448        @Override
449        public void startCDATA() {
450                OGNode node = new OGNode();
451                node.setNodeType( OGNodeType.Cdata );
452
453                ele.addNode( node );
454                stack.push( ele );
455                ele = node ;
456                inCDATA = true;
457        }
458
459        /**
460         * CDATA セクションの終わりを報告します。
461         *
462         * インタフェース LexicalHandler 内の endCDATA
463         *
464         * @see org.xml.sax.ext.DefaultHandler2#endCDATA()
465         * @see org.xml.sax.ext.LexicalHandler#endCDATA()
466         */
467        @Override
468        public void endCDATA() {
469                ele = stack.pop();
470                inCDATA = false;
471        }
472
473        /**
474         * DTD 宣言がある場合、その開始を報告します。
475         *
476         * start/endDTD イベントは、ContentHandler の
477         * start/endDocument イベント内の最初の startElement イベントの前に出現します。
478         *
479         * インタフェース LexicalHandler 内の startDTD
480         *
481         * @param       name    文書型名
482         * @param       publicId        宣言された外部 DTD サブセットの公開識別子。 宣言されていない場合は null
483         * @param       systemId        宣言された外部 DTD サブセットのシステム識別子。 宣言されていない場合は null。
484         *                ドキュメントのベース URI に対しては解決されないことに 注意すること
485         * @see org.xml.sax.ext.DefaultHandler2#startDTD( String , String , String )
486         * @see org.xml.sax.ext.LexicalHandler#startDTD( String , String , String )
487         */
488        @Override
489        public void startDTD( final String name, final String publicId, final String systemId ) {
490                StringBuilder buf = new StringBuilder();
491                buf.append( "<!DOCTYPE " ).append( name );
492                if( publicId != null ) { buf.append( " PUBLIC \"" ).append( publicId ).append( "\"" ); }
493                if( systemId != null ) { buf.append( "\"" ).append( systemId).append( "\"" ); }
494
495                OGNode node = new OGNode( buf.toString() );
496                node.setNodeType( OGNodeType.DTD );
497                ele.addNode( node );
498        }
499
500        /**
501         * DTD 宣言の終わりを報告します。
502         *
503         * このメソッドは、DOCTYPE 宣言の終わりを報告するメソッドです。
504         * ここでは、何もしません。
505         *
506         * インタフェース LexicalHandler 内の endDTD
507         *
508         * @see org.xml.sax.ext.DefaultHandler2#endDTD()
509         * @see org.xml.sax.ext.LexicalHandler#endDTD()
510         */
511        @Override
512        public void endDTD() {
513                // ここでは何もしません。
514        }
515
516        /**
517         * 内部および外部の XML エンティティーの一部の開始を報告します。
518         *
519         * インタフェース LexicalHandler の記述:
520         *
521         * ※ ここでは、&amp;lt; などの文字列が、lt という名のエンティティーで
522         * 報告されるため、元の&付きの文字列に復元しています。
523         * エンティティー内かどうかを判断する、inEntity フラグを true にセットします。
524         * inEntity=true の間は、#characters(char[],int,int) は、何も処理しません。
525         *
526         * @param       name    エンティティーの名前
527         * @see org.xml.sax.ext.LexicalHandler#startEntity(String)
528         */
529        @Override
530        public void startEntity( final String name ) {
531                String text = "&" + name + ";" ;
532                OGNode node = new OGNode( text );
533                ele.addNode( node );
534                inEntity = true;
535        }
536
537        /**
538         * エンティティーの終わりを報告します。
539         *
540         * インタフェース LexicalHandler の記述:
541         *
542         * ※ ここでは、inEntity=false を設定するだけです。
543         *
544         * @param       name    エンティティーの名前
545         * @see org.xml.sax.ext.LexicalHandler#endEntity(String)
546         */
547        @Override
548        public void endEntity( final String name ) {
549                inEntity = false;
550        }
551
552        /**
553         * 要素コンテンツに含まれる無視できる空白文字の通知を受け取ります。
554         *
555         * インタフェース ContentHandler 内の ignorableWhitespace
556         *
557         * @param       cbuf    文字データ配列(空白文字)
558         * @param       off             文字配列内の開始位置
559         * @param       len             文字配列から使用される文字数
560         *
561         * @see org.xml.sax.ContentHandler#ignorableWhitespace(char[],int,int)
562         */
563        @Override
564        public void ignorableWhitespace( final char[] cbuf, final int off, final int len ) {
565                String text = toText( cbuf,off,len );
566                OGNode node = new OGNode( text );
567                ele.addNode( node );
568        }
569
570        /**
571         * 文書内の任意の位置にある XML コメントを報告します。
572         *
573         * インタフェース LexicalHandler の記述:
574         *
575         * @param       cbuf    文字データ配列(コメント文字)
576         * @param       off             配列内の開始位置
577         * @param       len             配列から読み取られる文字数
578         *
579         * @see org.xml.sax.helpers.DefaultHandler#characters(char[],int,int)
580         */
581        @Override
582        public void comment( final char[] cbuf, final int off, final int len ) {
583                String text = toText( cbuf,off,len );
584                OGNode node = new OGNode( text );
585                node.setNodeType( OGNodeType.Comment );
586                ele.addNode( node );
587        }
588
589        /**
590         * 要素の終了通知を受け取ります。
591         *
592         * @param       uri                     名前空間 URI。要素が名前空間 URI を持たない場合、または名前空間処理が実行されない場合は null
593         * @param       localName       前置修飾子を含まないローカル名。名前空間処理が行われない場合は空文字列
594         * @param       qName           接頭辞を持つ修飾名。修飾名を使用できない場合は空文字列
595         *
596         * @see org.xml.sax.helpers.DefaultHandler#endElement(String,String,String)
597         * @see org.xml.sax.ContentHandler#endElement(String,String,String)
598         */
599        @Override
600        public void endElement( final String uri, final String localName, final String qName ) {
601                ele = stack.pop();
602        }
603
604        /**
605         * パーサー警告の通知を受け取ります。
606         *
607         * インタフェース org.xml.sax.ErrorHandler 内の warning
608         *
609         * ここでは、パーサー警告の内容を標準エラーに表示します。
610         *
611         * @param       ex      例外として符号化された警告情報
612         * @see org.xml.sax.ErrorHandler#warning(SAXParseException)
613         */
614        @Override
615        public void warning( final SAXParseException ex ) {
616                String errMsg = ex.getMessage() + ":" + ex.getPublicId()
617                                        + CR + "\t" + filename  + " (" + ex.getLineNumber() + ")";
618                System.err.println( "WARNING:" + errMsg );
619        }
620
621        /**
622         * 文字配列から、文字列を作成します。(改行コードの統一)
623         *
624         * 処理的には、new String( cbuf,off,len ) ですが、XMLでリード
625         * されたファイルは、改行コードが、'\r'(CR:復帰)+ '\n'(LF:改行)ではなく、
626         * '\n'(LF:改行) のみに処理されます。(されるようです。規定不明)
627         * そこで、実行環境の改行コード(System.getProperty("line.separator"))と
628         * 置き換えます。
629         *
630         * @param       cbuf    文字データ配列
631         * @param       off             配列内の開始位置
632         * @param       len             配列から読み取られる文字数
633         *
634         * @return      最終的な、Stringオブジェクト
635         */
636        private String toText( final char[] cbuf, final int off, final int len ) {
637                String text = new String( cbuf,off,len );
638                return text.replaceAll( "\n", CR );
639        }
640
641        /**
642         * OGDocument を取得します。
643         *
644         * @return      最終的な、OGNodeオブジェクトに相当します
645         */
646        private OGDocument getDocument() {
647                OGDocument doc = null;
648                if( ele != null && ele.getNodeType() == OGNodeType.Document ) {
649                        doc = (OGDocument)ele;
650                        doc.setIdMap( idMap );
651                }
652                return doc;
653        }
654}