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