OSDN Git Service

Ver8.5.2.0
[opengion/opengionV8.git] / uap / webapps / gf / src / org / opengion / fukurou / xml / JspSaxParser.java
1 /*
2  * Copyright (c) 2009 The openGion Project.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
13  * either express or implied. See the License for the specific language
14  * governing permissions and limitations under the License.
15  */
16 package org.opengion.fukurou.xml;
17
18 import org.opengion.fukurou.system.OgRuntimeException ;                                 // 6.4.2.0 (2016/01/29)
19 import org.opengion.fukurou.system.Closer ;
20 import org.opengion.fukurou.util.FileUtil ;
21 import static org.opengion.fukurou.system.HybsConst.CR;                                 // 6.1.0.0 (2014/12/26) refactoring
22 import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE;              // 6.4.2.1 (2016/02/05) refactoring
23
24 import java.lang.reflect.InvocationTargetException;                                             // 7.0.0.0
25 import java.io.PrintWriter ;
26 import java.io.IOException ;
27 import java.io.File;
28 import java.io.StringReader ;
29 import java.util.Stack;
30 import java.util.List;
31 import java.util.ArrayList;
32 import java.util.concurrent.ConcurrentMap;                                                      // 6.4.3.3 (2016/03/04)
33 import java.util.concurrent.ConcurrentHashMap;                                          // 6.4.3.1 (2016/02/12) refactoring
34
35 import org.xml.sax.Attributes;
36 import org.xml.sax.ext.DefaultHandler2;
37 import org.xml.sax.InputSource ;
38 import org.xml.sax.SAXException;
39 import org.xml.sax.SAXParseException;
40 import javax.xml.parsers.SAXParserFactory;
41 import javax.xml.parsers.SAXParser;
42 import javax.xml.parsers.ParserConfigurationException;
43
44 /**
45  * JSP/XMLファイルを読み取って、OGNode/OGElement オブジェクトを取得する、パーサークラスです。
46  *
47  * 自分自身が、DefaultHandler2 を拡張していますので、パーサー本体になります。
48  * javax.xml.parsers および、org.w3c.dom の簡易処理を行います。
49  * read で、トップレベルの OGNode を読み込み、write で、ファイルに書き出します。
50  * 通常の W3C 系の オブジェクトを利用しないのは、属性の並び順を保障するためです。
51  * ただし、属性のタブ、改行は失われます。
52  * また、属性値に含まれるCR(復帰), LF(改行), TAB(タブ)は、 半角スペースに置き換えられます。
53  * これは、SAXParser 側での XML の仕様の関係で、属性は、正規化されるためです。
54  *
55  * @og.rev 5.1.8.0 (2010/07/01) 新規作成
56  * @og.rev 5.1.9.0 (2010/08/01) static メソッドを廃止。通常のオブジェクトクラスとして扱います。
57  *
58  * @version  5.0
59  * @author   Kazuhiko Hasegawa
60  * @since    JDK6.0,
61  */
62 public class JspSaxParser extends DefaultHandler2 {
63
64         private final List<JspParserFilter> filters = new ArrayList<>();        // 5.1.9.0 (2010/08/01)
65         private SAXParser parser        ;
66
67         // 以下、パース時に使用する変数。(パース毎に初期化する。)
68         /** 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。 */
69         private ConcurrentMap<String,OGElement> idMap   ;               // 6.4.3.3 (2016/03/04)
70         private Stack<OGNode>             stack ;
71
72         private OGNode  ele                             ;       // 現時点のエレメントノード
73         private boolean inCDATA                 ;       // CDATA エレメントの中かどうかの判定
74         private boolean inEntity                ;       // Entity の中かどうかの判定
75         private String  filename                ;       // 処理実行中のファイル名
76
77         /**
78          * XMLファイルを読み込み、OGDocument を返します。
79          *
80          * 内部的には、SAXParserFactory から、SAXParser を構築し、Property に、
81          * http://xml.org/sax/properties/lexical-handler を設定しています。
82          * コメントノードを処理するためです。
83          *
84          * @og.rev 5.1.9.0 (2010/08/01) static からノーマルに変更
85          *
86          * @param       aFile   XMLファイル
87          *
88          * @return      ファイルから読み取って構築したOGDocumentオブジェクト
89          * @og.rtnNotNull
90          */
91         public OGDocument read( final File aFile ) {
92                 filename = aFile.getAbsolutePath() ;
93
94                 try {
95                         if( parser == null ) {
96                                 // SAXパーサーファクトリを生成
97                                 final SAXParserFactory spfactory = SAXParserFactory.newInstance();
98
99                                 // 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 }