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