    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                     名前空間 ＵＲＩ。要素が名前空間 ＵＲＩ を持たない場合、または名前空間処理が実行されない場合は 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                     名前空間 ＵＲＩ。要素が名前空間 ＵＲＩ を持たない場合、または名前空間処理が実行されない場合は 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}