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