jdom或dom4j讀取xml文件時如何讓dtd驗證使用本地dtd文件或者不生效

一、寫在所有之前:
因爲dom4j和jdom在這個問題上處理的方法是一模一樣的,只是一個是SAXBuilder 一個SAXReader,這裏以jdom距離,至於dom4j只需要同理替換一下就可以了。
二、問題發生的情況
當你用jdom讀取一個有 dtd驗證的xml文件,同時你的網絡是不通的情況下。會出現以下錯誤:
1,代碼如下

package  dom;

import  java.io.File;

import  org.jdom.Document;
import  org.jdom.input.SAXBuilder;

public   class  TestJdom  {
    
public   static   void  main(String[] args)  {
        File file 
=   new  File( " ./src/dom/aiwf_aiService.xml " );
        
if  (file.exists())  {
            SAXBuilder builder 
=   new  SAXBuilder();
            
try   {
                Document doc 
=  builder.build(file);
                System.out.println(doc);
            }
  catch  (Exception e)  {
                e.printStackTrace();
            }

        }
  else   {
            System.out.println(
" can not find xml file: "
                    
+  file.getAbsolutePath());
        }

    }

}

2,xml文件

<? xml version="1.0" encoding="GBK" ?>
<! DOCTYPE workflow PUBLIC "-//OpenSymphony Group//DTD OSWorkflow 2.8//EN" "http://www.opensymphony.com/osworkflow/workflow_2_8.dtd" >
< workflow >
                ...............
</ workflow >


3,錯誤如下

java.net.SocketException: Permission denied: connect
    at java.net.PlainSocketImpl.socketConnect(Native Method)
    at java.net.PlainSocketImpl.doConnect(PlainSocketImpl.java:
333 )
    at java.net.PlainSocketImpl.connectToAddress(PlainSocketImpl.java:
195 )
    at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:
182 )
    at java.net.Socket.connect(Socket.java:
507 )
    at java.net.Socket.connect(Socket.java:
457 )
    at sun.net.NetworkClient.doConnect(NetworkClient.java:
157 )
    at sun.net.www.http.HttpClient.openServer(HttpClient.java:
365 )
    at sun.net.www.http.HttpClient.openServer(HttpClient.java:
477 )
    at sun.net.www.http.HttpClient.
< init > (HttpClient.java: 214 )
    at sun.net.www.http.HttpClient.New(HttpClient.java:
287 )
    at sun.net.www.http.HttpClient.New(HttpClient.java:
299 )
    at sun.net.www.protocol.http.HttpURLConnection.getNewHttpClient(HttpURLConnection.java:
792 )
    at sun.net.www.protocol.http.HttpURLConnection.plainConnect(HttpURLConnection.java:
744 )
    at sun.net.www.protocol.http.HttpURLConnection.connect(HttpURLConnection.java:
669 )
    at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:
913 )
    at com.sun.org.apache.xerces.internal.impl.XMLEntityManager.setupCurrentEntity(XMLEntityManager.java:
973 )
    at com.sun.org.apache.xerces.internal.impl.XMLEntityManager.startEntity(XMLEntityManager.java:
905 )
    at com.sun.org.apache.xerces.internal.impl.XMLEntityManager.startDTDEntity(XMLEntityManager.java:
872 )
    at com.sun.org.apache.xerces.internal.impl.XMLDTDScannerImpl.setInputSource(XMLDTDScannerImpl.java:
282 )
    at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl$DTDDispatcher.dispatch(XMLDocumentScannerImpl.java:
1021 )
    at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(XMLDocumentFragmentScannerImpl.java:
368 )
    at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:
834 )
    at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:
764 )
    at com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(XMLParser.java:
148 )
    at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.parse(AbstractSAXParser.java:
1242 )
    at org.jdom.input.SAXBuilder.build(SAXBuilder.java:
453 )
    at org.jdom.input.SAXBuilder.build(SAXBuilder.java:
810 )
    at org.jdom.input.SAXBuilder.build(SAXBuilder.java:
789 )
    at dom.TestJdom.main(TestJdom.java:
26 )


三、分析原因
當執行build的時候jdom分析到
DOCTYPE workflow PUBLIC "-/OpenSymphony Group//DTD OSWorkflow 2.8//EN" "http://www.opensymphony.com/osworkflow/workflow_2_8.dtd
就會去讀取 http://www.opensymphony.com/osworkflow/workflow_2_8.dtd   這裏的dtd文件來驗證,但是因爲網絡是不通的所以就會報socket錯誤。

四、解決辦法
1, 最開始查看jdom api發現了這樣一個方法
builder.setValidation(false);
這樣可以讓jdom不做驗 證,但是結果依然出問題,查了一下原因,說雖然不驗證但是還是會下載
2,參照jdom網站的FAQ  http://www.jdom.org/docs/faq.html#a0100
這 是原文內容

How do I keep the DTD from loading? Even when I turn off validation the parser tries to load the DTD file.

Even when validation is turned off, an XML parser will by default load the external DTD file in order to parse the DTD for external entity declarations. Xerces has a feature to turn off this behavior named "http://apache.org/xml/features/nonvalidating/load-external-dtd" and if you know you're using Xerces you can set this feature on the builder.

builder.setFeature(
  "http://apache.org/xml/features/nonvalidating/load-external-dtd", false);

If you're using another parser like Crimson, your best bet is to set up an EntityResolver that resolves the DTD without actually reading the separate file.

import org.xml.sax.*;
import java.io.*;

public class NoOpEntityResolver implements EntityResolver {
  public InputSource resolveEntity(String publicId, String systemId) {
    return new InputSource(new StringBufferInputStream(""));
  }
}

Then in the builder


builder.setEntityResolver(new NoOpEntityResolver());

There is a downside to this approach. Any entities in the document will be resolved to the empty string, and will effectively disappear. If your document has entities, you need to setExpandEntities(false) code and ensure the EntityResolver only suppresses the DocType.
裏邊教我們定義個類
public   class  NoOpEntityResolver  implements  EntityResolver  {
  
public  InputSource resolveEntity(String publicId, String systemId)  {
            
return   new  InputSource( new  StringBufferInputStream( "" ));
  }

}

通過builder.setEntityResolver(new NoOpEntityResolver())方法來隱蔽起dtd驗證器。這樣就 不會出錯了。試了一下確實沒問題了。但要知道xml沒有dtd驗證是不好的,我們是否能讓它使用本地dtd驗證呢。例如本文的oswork
我把驗 證文件workflow_2_8.dtd拷貝到本地,能否驗證的時候用本地的呢?
3,用本地dtd驗證
方法有兩種
方法一、更改 xml中的doctype聲明,但是一般情況下更改這個是不好的。更改後就不是標準的了。
方法二、驗證期替換
看到上邊FAQ講的方法你是 否有什麼靈感呢?
看看下邊這段代碼

package  dom;

import  java.io.File;
import  java.io.IOException;

import  org.jdom.Document;
import  org.jdom.input.SAXBuilder;
import  org.xml.sax.EntityResolver;
import  org.xml.sax.InputSource;
import  org.xml.sax.SAXException;

public   class  TestJdom  {
    
public   static   void  main(String[] args)  {
        File file 
=   new  File( " ./src/dom/aiwf_aiService.xml " );
        
if  (file.exists())  {
            SAXBuilder builder 
=   new  SAXBuilder();
            builder.setValidation(
false );
            builder.setEntityResolver(
new  EntityResolver()  {
                
public  InputSource resolveEntity(String publicId,
                        String systemId) 
throws  SAXException, IOException  {
                    
return   new  InputSource( " ./workflow_2_8.dtd " );
                }

            }
);
            
try   {
                Document doc 
=  builder.build(file);
                System.out.println(doc);
            }
  catch  (Exception e)  {
                e.printStackTrace();
            }

        }
  else   {
            System.out.println(
" can not find xml file: "
                    
+  file.getAbsolutePath());
        }

    }

}

對了,同樣是自己實現一個EntityResolver(這裏用了匿名類),不同的是在裏邊使用本地的dtd驗證
另外,匿名類內部,似乎這 樣寫起來更順眼些

InputStream stream  =   new  FileInputStream(  " your dtd file path "  );
                    InputSource is 
=   new  InputSource(stream);
                    is.setPublicId(publicId);
                    is.setSystemId(systemId);
                    
return  is;
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章