2 * Copyright (c) 2009 The openGion Project.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package org.opengion.fukurou.xml;
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
24 import java.lang.reflect.InvocationTargetException; // 7.0.0.0
25 import java.io.PrintWriter ;
26 import java.io.IOException ;
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
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;
45 * JSP/XMLファイルを読み取って、OGNode/OGElement オブジェクトを取得する、パーサークラスです。
47 * 自分自身が、DefaultHandler2 を拡張していますので、パーサー本体になります。
48 * javax.xml.parsers および、org.w3c.dom の簡易処理を行います。
49 * read で、トップレベルの OGNode を読み込み、write で、ファイルに書き出します。
50 * 通常の W3C 系の オブジェクトを利用しないのは、属性の並び順を保障するためです。
52 * また、属性値に含まれるCR(復帰), LF(改行), TAB(タブ)は、 半角スペースに置き換えられます。
53 * これは、SAXParser 側での XML の仕様の関係で、属性は、正規化されるためです。
55 * @og.rev 5.1.8.0 (2010/07/01) 新規作成
56 * @og.rev 5.1.9.0 (2010/08/01) static メソッドを廃止。通常のオブジェクトクラスとして扱います。
59 * @author Kazuhiko Hasegawa
62 public class JspSaxParser extends DefaultHandler2 {
64 private final List<JspParserFilter> filters = new ArrayList<>(); // 5.1.9.0 (2010/08/01)
65 private SAXParser parser ;
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 ;
72 private OGNode ele ; // 現時点のエレメントノード
73 private boolean inCDATA ; // CDATA エレメントの中かどうかの判定
74 private boolean inEntity ; // Entity の中かどうかの判定
75 private String filename ; // 処理実行中のファイル名
78 * XMLファイルを読み込み、OGDocument を返します。
80 * 内部的には、SAXParserFactory から、SAXParser を構築し、Property に、
81 * http://xml.org/sax/properties/lexical-handler を設定しています。
84 * @og.rev 5.1.9.0 (2010/08/01) static からノーマルに変更
86 * @param aFile XMLファイル
88 * @return ファイルから読み取って構築したOGDocumentオブジェクト
91 public OGDocument read( final File aFile ) {
92 filename = aFile.getAbsolutePath() ;
95 if( parser == null ) {
97 final SAXParserFactory spfactory = SAXParserFactory.newInstance();
100 parser = spfactory.newSAXParser();
102 parser.setProperty("http://xml.org/sax/properties/lexical-handler", this); // LexicalHandler として
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 );
144 return getDocument() ;
148 * XML形式で表現された、文字列(String) から、OGDocument を構築します。
150 * 処理的には、#read( File ) と同じで、取り出す元が、文字列というだけです。
151 * XMLファイルからの読み込みと異なり、通常は、Element を表現した文字列が作成されますが、
152 * 返されるのは、OGDocument オブジェクトです。
154 * @og.rev 5.1.9.0 (2010/08/01) static からノーマルに変更
156 * @param str XML形式で表現された文字列
158 * @return ファイルから読み取って構築した OGDocumentオブジェクト
161 public OGDocument string2Node( final String str ) {
165 if( parser == null ) {
167 final SAXParserFactory spfactory = SAXParserFactory.newInstance();
169 parser = spfactory.newSAXParser();
171 parser.setProperty("http://xml.org/sax/properties/lexical-handler", this); // LexicalHandler として
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 );
205 return getDocument() ;
209 * OGDocument を所定のファイルに、XML形式で書き出します。
211 * @og.rev 6.3.8.0 (2015/09/11) FileUtil#getPrintWriter( File,String ) を使用。
213 * @param aFile 書き出すファイル
214 * @param node 書き出す OGDocument
216 public void write( final File aFile, final OGDocument node ) {
217 PrintWriter out = null;
218 final String encode = node.getEncode();
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() );
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 );
233 Closer.ioClose( out );
238 * ディレクトリの再帰処理でパース処理を行います。
240 * @og.rev 5.1.9.0 (2010/08/01) static からノーマルに変更
242 * @param fromFile 読み取りもとのファイル/フォルダ
243 * @param toFile 書き込み先のファイル/フォルダ
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" ) ) {
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; } // エラー、または処理の中止
264 catch( final RuntimeException ex ) {
265 // ex.printStackTrace();
266 System.out.println( ex.getMessage() );
270 // JSPやXMLでない、パースエラー、書き出しエラーなど正常終了できなかった場合は、バイナリコピー
272 FileUtil.copy( fromFile,toFile,true );
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 + " の ディレクトリ作成に失敗しました。" );
284 // ディレクトリ内のファイルをすべて取得する
285 final File[] files = fromFile.listFiles();
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()) );
297 * copyDirectry 処理で、OGDocument をフィルター処理するオブジェクトを登録します。
300 * フィルター処理は、追加された順に行われます。
301 * 内部リストへの追加はできますが、削除はできません。
303 * @og.rev 5.1.9.0 (2010/08/01) 新規追加
305 * @param filter フィルターオブジェクト
307 public void addFilter( final JspParserFilter filter ) {
308 filters.add( filter );
311 // ********************************************************************************************** //
313 // ** ここから下は、DefaultHandler2 の実装になります。 ** //
315 // ********************************************************************************************** //
320 * インタフェース ContentHandler 内の startDocument
322 * @og.rev 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。
324 * @see org.xml.sax.helpers.DefaultHandler#startDocument()
325 * @see org.xml.sax.ContentHandler#startDocument()
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 );
337 ele = doc; // OGDocument に、setFilename(String) してから、代入します。
338 inCDATA = false; // CDATA エレメントの中かどうかの判定
339 inEntity = false; // Entity の中かどうかの判定
345 * インタフェース ContentHandler 内の startElement
347 * @param uri 名前空間 URI。要素が名前空間 URI を持たない場合、または名前空間処理が実行されない場合は null
348 * @param localName 前置修飾子を含まないローカル名。名前空間処理が行われない場合は空文字列
349 * @param qName 接頭辞を持つ修飾名。修飾名を使用できない場合は空文字列
350 * @param attributes 要素に付加された属性。属性が存在しない場合、空の Attributesオブジェクト
352 * @see org.xml.sax.helpers.DefaultHandler#startElement(String,String,String,Attributes)
353 * @see org.xml.sax.ContentHandler#startElement(String,String,String,Attributes)
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にキャッシュ
361 ele.addNode( newEle );
367 * 要素内の文字データの通知を受け取ります。
369 * エンティティー内かどうかを判断する、inEntity フラグが true の間は、
372 * インタフェース ContentHandler 内の characters
374 * @param cbuf 文字データ配列
375 * @param off 文字配列内の開始位置
376 * @param len 文字配列から使用される文字数
378 * @see org.xml.sax.helpers.DefaultHandler#characters(char[],int,int)
379 * @see org.xml.sax.ContentHandler#characters(char[],int,int)
382 public void characters( final char[] cbuf, final int off, final int len ) {
383 if( inEntity ) { return ; } // < ⇒ < に変換されるので、エンティティ内では、なにも処理しない。
385 final String text = toText( cbuf,off,len );
391 final OGNode node = new OGNode( text );
394 // 6.0.2.5 (2014/10/31) refactoring 読み出されないフィールド:attTab
395 // '\r'(CR:復帰)+ '\n'(LF:改行)の可能性があるが、 '\n'(LF:改行)が、より後ろにあるので、これで判定。
399 * CDATA セクションの開始を報告します。
401 * CDATA セクションのコンテンツは、正規の characters イベントを介して報告されます。
402 * このイベントは境界の報告だけに使用されます。
404 * インタフェース LexicalHandler 内の startCDATA
406 * @see org.xml.sax.ext.DefaultHandler2#startCDATA()
407 * @see org.xml.sax.ext.LexicalHandler#startCDATA()
410 public void startCDATA() {
411 final OGNode node = new OGNode();
412 node.setNodeType( OGNodeType.Cdata );
421 * CDATA セクションの終わりを報告します。
423 * インタフェース LexicalHandler 内の endCDATA
425 * @see org.xml.sax.ext.DefaultHandler2#endCDATA()
426 * @see org.xml.sax.ext.LexicalHandler#endCDATA()
429 public void endCDATA() {
435 * DTD 宣言がある場合、その開始を報告します。
437 * start/endDTD イベントは、ContentHandler の
438 * start/endDocument イベント内の最初の startElement イベントの前に出現します。
440 * インタフェース LexicalHandler 内の startDTD
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 )
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( '"' ); }
457 final OGNode node = new OGNode( buf.toString() );
458 node.setNodeType( OGNodeType.DTD );
465 * このメソッドは、DOCTYPE 宣言の終わりを報告するメソッドです。
468 * インタフェース LexicalHandler 内の endDTD
470 * @see org.xml.sax.ext.DefaultHandler2#endDTD()
471 * @see org.xml.sax.ext.LexicalHandler#endDTD()
474 public void endDTD() {
479 * 内部および外部の XML エンティティーの一部の開始を報告します。
481 * インタフェース LexicalHandler の記述:
483 * ※ ここでは、&lt; などの文字列が、lt という名のエンティティーで
484 * 報告されるため、元の&付きの文字列に復元しています。
485 * エンティティー内かどうかを判断する、inEntity フラグを true にセットします。
486 * inEntity=true の間は、#characters(char[],int,int) は、何も処理しません。
488 * @param name エンティティーの名前
489 * @see org.xml.sax.ext.LexicalHandler#startEntity(String)
492 public void startEntity( final String name ) {
493 final String text = "&" + name + ";" ;
494 final OGNode node = new OGNode( text );
502 * インタフェース LexicalHandler の記述:
504 * ※ ここでは、inEntity=false を設定するだけです。
506 * @param name エンティティーの名前
507 * @see org.xml.sax.ext.LexicalHandler#endEntity(String)
510 public void endEntity( final String name ) {
515 * 要素コンテンツに含まれる無視できる空白文字の通知を受け取ります。
517 * インタフェース ContentHandler 内の ignorableWhitespace
519 * @param cbuf 文字データ配列(空白文字)
520 * @param off 文字配列内の開始位置
521 * @param len 文字配列から使用される文字数
523 * @see org.xml.sax.ContentHandler#ignorableWhitespace(char[],int,int)
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 );
533 * 文書内の任意の位置にある XML コメントを報告します。
535 * インタフェース LexicalHandler の記述:
537 * @param cbuf 文字データ配列(コメント文字)
538 * @param off 配列内の開始位置
539 * @param len 配列から読み取られる文字数
541 * @see org.xml.sax.helpers.DefaultHandler#characters(char[],int,int)
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 );
554 * @param uri 名前空間 URI。要素が名前空間 URI を持たない場合、または名前空間処理が実行されない場合は null
555 * @param localName 前置修飾子を含まないローカル名。名前空間処理が行われない場合は空文字列
556 * @param qName 接頭辞を持つ修飾名。修飾名を使用できない場合は空文字列
558 * @see org.xml.sax.helpers.DefaultHandler#endElement(String,String,String)
559 * @see org.xml.sax.ContentHandler#endElement(String,String,String)
562 public void endElement( final String uri, final String localName, final String qName ) {
569 * インタフェース org.xml.sax.ErrorHandler 内の warning
571 * ここでは、パーサー警告の内容を標準エラーに表示します。
573 * @param ex 例外として符号化された警告情報
574 * @see org.xml.sax.ErrorHandler#warning(SAXParseException)
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 );
584 * 文字配列から、文字列を作成します。(改行コードの統一)
586 * 処理的には、new String( cbuf,off,len ) ですが、XMLでリード
587 * されたファイルは、改行コードが、'\r'(CR:復帰)+ '\n'(LF:改行)ではなく、
588 * '\n'(LF:改行) のみに処理されます。(されるようです。規定不明)
589 * そこで、実行環境の改行コード(System.getProperty("line.separator"))と
592 * @param cbuf 文字データ配列
593 * @param off 配列内の開始位置
594 * @param len 配列から読み取られる文字数
596 * @return 最終的な、Stringオブジェクト
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 );
607 * @return 最終的な、OGNodeオブジェクトに相当します
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 );
618 final String errMsg = "この、OGNode は、OGDocument のインスタンスではありません。" ;
619 System.err.println( "WARNING:" + errMsg );
628 * 引数の IN がファイルの場合は、OUTもファイルとして扱います。
629 * IN がフォルダの場合は、階層にしたがって、再帰的に処理を行い、OUT に出力します。
630 * フォルダ階層をパースしている最中に、XMLとして処理できない、処理中にエラーが発生した
631 * などの場合は、バイナリコピーを行います。
633 * "Usage: org.opengion.fukurou.xml.JspSaxParser <inFile|inDir> <outFile|outDir> [<JspParserFilter1> ・・・ ]"
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())
639 * @param args コマンド引数配列
640 * @throws ClassNotFoundException クラスが見つからない場合
641 * @throws InstantiationException インスタンスを生成できなかった場合
642 * @throws IllegalAccessException 不正なアクセスがあった場合
643 * @throws NoSuchMethodException 特定のメソッドが見つからない
644 * @throws InvocationTargetException 呼び出されるメソッドまたはコンストラクタがスローする例外をラップする、チェック済み例外
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> ・・・ ]" );
651 final File in = new File( args[0] );
652 final File out = new File( args[1] );
654 final JspSaxParser jsp = new JspSaxParser();
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 );
663 jsp.copyDirectry( in,out );