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