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                stack   = new Stack<>();
332                ele             = new OGDocument();
333                ((OGDocument)ele).setFilename( filename );
334
335                idMap   = new ConcurrentHashMap<>();    // 6.4.3.1 (2016/02/12)
336
337                inCDATA  = false;       // CDATA エレメントの中かどうかの判定
338                inEntity = false;       // Entity の中かどうかの判定
339        }
340
341        /**
342         * 要素の開始通知を受け取ります。
343         *
344         * インタフェース ContentHandler 内の startElement
345         *
346         * @param       uri                     名前空間 URI。要素が名前空間 URI を持たない場合、または名前空間処理が実行されない場合は null
347         * @param       localName       前置修飾子を含まないローカル名。名前空間処理が行われない場合は空文字列
348         * @param       qName           接頭辞を持つ修飾名。修飾名を使用できない場合は空文字列
349         * @param       attributes      要素に付加された属性。属性が存在しない場合、空の Attributesオブジェクト
350         *
351         * @see org.xml.sax.helpers.DefaultHandler#startElement(String,String,String,Attributes)
352         * @see org.xml.sax.ContentHandler#startElement(String,String,String,Attributes)
353         */
354        @Override
355        public void startElement( final String uri, final String localName, final String qName, final Attributes attributes ) {
356                final OGElement newEle = new OGElement( qName,attributes );
357                final String id = newEle.getId();
358                if( id != null ) { idMap.put( id,newEle ); }            // 5.1.9.0 (2010/08/01) idをMapにキャッシュ
359
360                ele.addNode( newEle );
361                stack.push( ele );
362                ele = newEle ;
363        }
364
365        /**
366         * 要素内の文字データの通知を受け取ります。
367         *
368         * エンティティー内かどうかを判断する、inEntity フラグが true の間は、
369         * 何も処理しません。
370         *
371         * インタフェース ContentHandler 内の characters
372         *
373         * @param       cbuf    文字データ配列
374         * @param       off             文字配列内の開始位置
375         * @param       len             文字配列から使用される文字数
376         *
377         * @see org.xml.sax.helpers.DefaultHandler#characters(char[],int,int)
378         * @see org.xml.sax.ContentHandler#characters(char[],int,int)
379         */
380        @Override
381        public void characters( final char[] cbuf, final int off, final int len ) {
382                if( inEntity ) { return ; }             // &lt; ⇒ < に変換されるので、エンティティ内では、なにも処理しない。
383
384                final String text = toText( cbuf,off,len );
385                if( inCDATA ) {
386                        ele.addNode( text );
387                        return ;
388                }
389
390                final OGNode node = new OGNode( text );
391                ele.addNode( node );
392
393                // 6.0.2.5 (2014/10/31) refactoring 読み出されないフィールド:attTab
394                // '\r'(CR:復帰)+ '\n'(LF:改行)の可能性があるが、 '\n'(LF:改行)が、より後ろにあるので、これで判定。
395        }
396
397        /**
398         * CDATA セクションの開始を報告します。
399         *
400         * CDATA セクションのコンテンツは、正規の characters イベントを介して報告されます。
401         * このイベントは境界の報告だけに使用されます。
402         *
403         * インタフェース LexicalHandler 内の startCDATA
404         *
405         * @see org.xml.sax.ext.DefaultHandler2#startCDATA()
406         * @see org.xml.sax.ext.LexicalHandler#startCDATA()
407         */
408        @Override
409        public void startCDATA() {
410                final OGNode node = new OGNode();
411                node.setNodeType( OGNodeType.Cdata );
412
413                ele.addNode( node );
414                stack.push( ele );
415                ele = node ;
416                inCDATA = true;
417        }
418
419        /**
420         * CDATA セクションの終わりを報告します。
421         *
422         * インタフェース LexicalHandler 内の endCDATA
423         *
424         * @see org.xml.sax.ext.DefaultHandler2#endCDATA()
425         * @see org.xml.sax.ext.LexicalHandler#endCDATA()
426         */
427        @Override
428        public void endCDATA() {
429                ele = stack.pop();
430                inCDATA = false;
431        }
432
433        /**
434         * DTD 宣言がある場合、その開始を報告します。
435         *
436         * start/endDTD イベントは、ContentHandler の
437         * start/endDocument イベント内の最初の startElement イベントの前に出現します。
438         *
439         * インタフェース LexicalHandler 内の startDTD
440         *
441         * @param       name    文書型名
442         * @param       publicId        宣言された外部 DTD サブセットの公開識別子。 宣言されていない場合は null
443         * @param       systemId        宣言された外部 DTD サブセットのシステム識別子。 宣言されていない場合は null。
444         *                ドキュメントのベース URI に対しては解決されないことに 注意すること
445         * @see org.xml.sax.ext.DefaultHandler2#startDTD( String , String , String )
446         * @see org.xml.sax.ext.LexicalHandler#startDTD( String , String , String )
447         */
448        @Override
449        public void startDTD( final String name, final String publicId, final String systemId ) {
450                // 6.0.2.5 (2014/10/31) char を append する。
451                final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE )
452                        .append( "<!DOCTYPE " ).append( name );
453                if( publicId != null ) { buf.append( " PUBLIC \"" ).append( publicId ).append( '"' ); }
454                if( systemId != null ) { buf.append( '"' ).append( systemId ).append( '"' ); }
455
456                final OGNode node = new OGNode( buf.toString() );
457                node.setNodeType( OGNodeType.DTD );
458                ele.addNode( node );
459        }
460
461        /**
462         * DTD 宣言の終わりを報告します。
463         *
464         * このメソッドは、DOCTYPE 宣言の終わりを報告するメソッドです。
465         * ここでは、何もしません。
466         *
467         * インタフェース LexicalHandler 内の endDTD
468         *
469         * @see org.xml.sax.ext.DefaultHandler2#endDTD()
470         * @see org.xml.sax.ext.LexicalHandler#endDTD()
471         */
472        @Override
473        public void endDTD() {
474                // ここでは何もしません。
475        }
476
477        /**
478         * 内部および外部の XML エンティティーの一部の開始を報告します。
479         *
480         * インタフェース LexicalHandler の記述:
481         *
482         * ※ ここでは、&amp;lt; などの文字列が、lt という名のエンティティーで
483         * 報告されるため、元の&付きの文字列に復元しています。
484         * エンティティー内かどうかを判断する、inEntity フラグを true にセットします。
485         * inEntity=true の間は、#characters(char[],int,int) は、何も処理しません。
486         *
487         * @param       name    エンティティーの名前
488         * @see org.xml.sax.ext.LexicalHandler#startEntity(String)
489         */
490        @Override
491        public void startEntity( final String name ) {
492                final String text = "&" + name + ";" ;
493                final OGNode node = new OGNode( text );
494                ele.addNode( node );
495                inEntity = true;
496        }
497
498        /**
499         * エンティティーの終わりを報告します。
500         *
501         * インタフェース LexicalHandler の記述:
502         *
503         * ※ ここでは、inEntity=false を設定するだけです。
504         *
505         * @param       name    エンティティーの名前
506         * @see org.xml.sax.ext.LexicalHandler#endEntity(String)
507         */
508        @Override
509        public void endEntity( final String name ) {
510                inEntity = false;
511        }
512
513        /**
514         * 要素コンテンツに含まれる無視できる空白文字の通知を受け取ります。
515         *
516         * インタフェース ContentHandler 内の ignorableWhitespace
517         *
518         * @param       cbuf    文字データ配列(空白文字)
519         * @param       off             文字配列内の開始位置
520         * @param       len             文字配列から使用される文字数
521         *
522         * @see org.xml.sax.ContentHandler#ignorableWhitespace(char[],int,int)
523         */
524        @Override
525        public void ignorableWhitespace( final char[] cbuf, final int off, final int len ) {
526                final String text = toText( cbuf,off,len );
527                final OGNode node = new OGNode( text );
528                ele.addNode( node );
529        }
530
531        /**
532         * 文書内の任意の位置にある XML コメントを報告します。
533         *
534         * インタフェース LexicalHandler の記述:
535         *
536         * @param       cbuf    文字データ配列(コメント文字)
537         * @param       off             配列内の開始位置
538         * @param       len             配列から読み取られる文字数
539         *
540         * @see org.xml.sax.helpers.DefaultHandler#characters(char[],int,int)
541         */
542        @Override
543        public void comment( final char[] cbuf, final int off, final int len ) {
544                final String text = toText( cbuf,off,len );
545                final OGNode node = new OGNode( text );
546                node.setNodeType( OGNodeType.Comment );
547                ele.addNode( node );
548        }
549
550        /**
551         * 要素の終了通知を受け取ります。
552         *
553         * @param       uri                     名前空間 URI。要素が名前空間 URI を持たない場合、または名前空間処理が実行されない場合は null
554         * @param       localName       前置修飾子を含まないローカル名。名前空間処理が行われない場合は空文字列
555         * @param       qName           接頭辞を持つ修飾名。修飾名を使用できない場合は空文字列
556         *
557         * @see org.xml.sax.helpers.DefaultHandler#endElement(String,String,String)
558         * @see org.xml.sax.ContentHandler#endElement(String,String,String)
559         */
560        @Override
561        public void endElement( final String uri, final String localName, final String qName ) {
562                ele = stack.pop();
563        }
564
565        /**
566         * パーサー警告の通知を受け取ります。
567         *
568         * インタフェース org.xml.sax.ErrorHandler 内の warning
569         *
570         * ここでは、パーサー警告の内容を標準エラーに表示します。
571         *
572         * @param       ex      例外として符号化された警告情報
573         * @see org.xml.sax.ErrorHandler#warning(SAXParseException)
574         */
575        @Override
576        public void warning( final SAXParseException ex ) {
577                final String errMsg = ex.getMessage() + ":" + ex.getPublicId()
578                                        + CR + "\t" + filename  + " (" + ex.getLineNumber() + ")";
579                System.err.println( "WARNING:" + errMsg );
580        }
581
582        /**
583         * 文字配列から、文字列を作成します。(改行コードの統一)
584         *
585         * 処理的には、new String( cbuf,off,len ) ですが、XMLでリード
586         * されたファイルは、改行コードが、'\r'(CR:復帰)+ '\n'(LF:改行)ではなく、
587         * '\n'(LF:改行) のみに処理されます。(されるようです。規定不明)
588         * そこで、実行環境の改行コード(System.getProperty("line.separator"))と
589         * 置き換えます。
590         *
591         * @param       cbuf    文字データ配列
592         * @param       off             配列内の開始位置
593         * @param       len             配列から読み取られる文字数
594         *
595         * @return      最終的な、Stringオブジェクト
596         * @og.rtnNotNull
597         */
598        private String toText( final char[] cbuf, final int off, final int len ) {
599                final String text = new String( cbuf,off,len );
600                return text.replaceAll( "\n", CR );
601        }
602
603        /**
604         * OGDocument を取得します。
605         *
606         * @return      最終的な、OGNodeオブジェクトに相当します
607         */
608        private OGDocument getDocument() {
609                OGDocument doc = null;
610                if( ele != null && ele.getNodeType() == OGNodeType.Document ) {
611                        // 6.0.2.5 (2014/10/31) refactoring: getNodeType でチェックしているので間違いはないが、findBugs対応
612                        if( ele instanceof OGDocument ) {
613                                doc = (OGDocument)ele;
614                                doc.setIdMap( idMap );
615                        }
616                        else {                  // 基本、あり得ない。
617                                final String errMsg = "この、OGNode は、OGDocument のインスタンスではありません。" ;
618                                System.err.println( "WARNING:" + errMsg );
619                        }
620                }
621                return doc;
622        }
623
624        /**
625         * サンプルプログラムです。
626         *
627         * 引数の IN がファイルの場合は、OUTもファイルとして扱います。
628         * IN がフォルダの場合は、階層にしたがって、再帰的に処理を行い、OUT に出力します。
629         * フォルダ階層をパースしている最中に、XMLとして処理できない、処理中にエラーが発生した
630         * などの場合は、バイナリコピーを行います。
631         *
632         * "Usage: org.opengion.fukurou.xml.JspSaxParser  &lt;inFile|inDir&gt; &lt;outFile|outDir&gt; [&lt;JspParserFilter1&gt; ・・・ ]"
633         *
634         * @og.rev 6.3.9.1 (2015/11/27) A method/constructor shouldnt explicitly throw java.lang.Exception(PMD)。
635         * @og.rev 6.4.3.3 (2016/03/04) リフレクション系の例外の共通クラスに置き換えます。
636         * @og.rev 6.8.2.3 (2017/11/10) java9対応(cls.newInstance() → cls.getDeclaredConstructor().newInstance())
637         *
638         * @param       args    コマンド引数配列
639         * @throws ClassNotFoundException クラスが見つからない場合
640         * @throws InstantiationException インスタンスを生成できなかった場合
641         * @throws IllegalAccessException 不正なアクセスがあった場合
642         * @throws NoSuchMethodException 特定のメソッドが見つからない
643         * @throws InvocationTargetException 呼び出されるメソッドまたはコンストラクタがスローする例外をラップする、チェック済み例外
644         */
645        public static void main( final String[] args ) throws ReflectiveOperationException , NoSuchMethodException , InvocationTargetException {        // 6.8.2.3 (2017/11/10)
646                if( args.length < 2 ) {
647                        System.out.println( "Usage: org.opengion.fukurou.xml.JspSaxParser <inFile|inDir> <outFile|outDir> [<JspParserFilter1> ・・・ ]" );
648                }
649
650                final File in   = new File( args[0] );
651                final File out  = new File( args[1] );
652
653                final JspSaxParser jsp = new JspSaxParser();
654
655                if( args.length >= 3 ) {
656                        for( int i=2; i<args.length; i++ ) {
657                                final JspParserFilter filter = (JspParserFilter)Class.forName( args[i] ).getDeclaredConstructor().newInstance();                // 6.8.2.3 (2017/11/10)
658                                jsp.addFilter( filter );
659                        }
660                }
661
662                jsp.copyDirectry( in,out );
663        }
664}