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