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                     名前空間 URI。要素が名前空間 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                     名前空間 URI。要素が名前空間 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}