打开extern DTD(w3.org,xhtml1-transitional.dtd)时出错。 503服务器不可用

时间:2010-04-01 03:49:20

标签: .net xml xhtml w3c dtd

我正在尝试对xhtml文档进行xpath查询。使用.NET 3.5。

该文件如下:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
  <head>
   ....
  </head>
  <body>
    ...
  </body>
</html>

因为文档包含各种char实体(&nbsp;等),所以我需要使用DTD,以便用XmlReader加载它。所以我的代码看起来像这样:

var s = File.OpenRead(fileToRead)
var reader = XmlReader.Create(s, new XmlReaderSettings{ ProhibitDtd=false });

但是当我运行它时,它会返回

  

打开外部DTD“http://www.w3.org/TR/xhtml1-transitional.dtd”时发生错误:远程服务器返回错误:(503)服务器不可用。

现在,我知道为什么我收到503错误。 W3C explained it very clearly

我见过“解决方法”,人们只是禁用DTD。这是ProhibitDtd=true可以做的,它消除了503错误。

但在我的情况下导致其他问题 - 应用程序没有得到实体定义,因此不是格式良好的XML。如何在不访问w3.org网站的情况下验证DTD并获取实体定义?


我认为.NET 4.0有一个非常好的内置功能来处理这种情况:XmlPreloadedResolver。但我需要.NET 3.5的解决方案。


相关


- java.io.IOException: Server returned HTTP response code: 503

3 个答案:

答案 0 :(得分:7)

答案是,我必须提供自己的XmlResolver。我不认为这是.NET 3.5内置的。那令人费解。令人费解的是,我花了很长时间才发现这个问题。令人费解的是,我找不到其他人已经解决了这个问题?

好的,所以...... XmlResolver。我创建了一个新类,派生自XmlResolver并过度使用三个关键内容:Credentials(set),ResolveUri和GetEntity。

public sealed class XhtmlResolver : XmlResolver
{
    public override System.Net.ICredentials Credentials
    {
        set { throw new NotSupportedException();}
    }

    public override object GetEntity(Uri absoluteUri, string role, Type t)
    {
       ...
    }

    public override Uri ResolveUri(Uri baseUri, string relativeUri)
    {
      ...
    }
}

关于这些东西的文档相当吝啬,所以我会告诉你我学到了什么。这个类的操作是这样的:XmlReader将首先调用ResolveUri,然后,给定一个已解析的Uri,然后调用GetEntity。期望该方法返回类型为t的对象(作为参数传递)。我只看到它请求System.IO.Stream。

我的想法是使用csc.exe /resource选项将XDML1.0的DTD及其依赖项的本地副本嵌入到程序集中,然后检索该资源的流。

private System.IO.Stream GetStreamForNamedResource(string resourceName)
{
    Assembly a = Assembly.GetExecutingAssembly();
    return  a.GetManifestResourceStream(resourceName);
}

非常简单。这是从GetEntity()调用的。

但我可以改进。我没有将DTD嵌入到明文中,而是先将它们解压缩。然后像这样修改上面的方法:

private System.IO.Stream GetStreamForNamedResource(string resourceName)
{
    Assembly a = Assembly.GetExecutingAssembly();
    return  new System.IO.Compression.GZipStream(a.GetManifestResourceStream(resourceName), System.IO.Compression.CompressionMode.Decompress);
}

该代码打开嵌入式资源的流,并返回为解压缩配置的GZipStream。读者获得了明文DTD。

我想要做的只是解析Xhtml 1.0中DTD的URI。所以我写了ResolveUri和GetEntity来寻找那些特定的DTD,并且只对它们作出肯定的回应。

对于带有DTD语句的XHTML文档,流程是这样的;

  1. XmlReader使用XHTML DTD的公共URI调用ResolveUri,即"-//W3C//DTD XHTML 1.0 Transitional//EN"。如果XmlResolver可以解析,它应该返回...一个有效的URI。如果它无法解决,它应该抛出。我的实现只针对公共URI。

  2. 然后,XmlReader使用DTD的系统标识符调用ResolveUri,在本例中为"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"。在这种情况下,XhtmlResolver返回一个有效的Uri。

  3. 然后XmlReader使用该URI调用GetEntity。 XhtmlResolver抓取嵌入的资源流并返回它。

  4. 依赖项也会发生同样的事情 - xhtml_lat1.ent,等等。为了使解析器工作,所有这些都需要嵌入。

    是的,如果解析器无法解析URI,则应该抛出异常。据我所知,这还没有正式记录。这似乎有点令人惊讶。 (严重违反the principle of least astonishment)。如果相反,ResolveUri返回null,XmlReader将在空URI上调用GetEntity,这......啊,是没有希望的。


    这对我有用。它适用于从.NET进行XHTML XML处理的任何人。如果您想在自己的应用程序中使用它,grab the DLL。该zip包含完整的源代码。根据{{​​3}}许可。

    您可以将其插入使用XHTML的XML应用程序中。像这样使用它:

    // for an XmlDocument...
    System.Xml.XmlDocument doc = new System.Xml.XmlDocument();
    doc.XmlResolver = new Ionic.Xml.XhtmlResolver();
    doc.Load(xhtmlFile);
    
    // for an XmlReader...
    var xmlReaderSettings = new XmlReaderSettings
        {
            ProhibitDtd = false,
            XmlResolver = new XhtmlResolver()
        };
    using (var stream = File.OpenRead(fileToRead))
    {
        XmlReader reader = XmlReader.Create(stream, xmlReaderSettings);
        while (reader.Read())
        {
         ...
        }
    

答案 1 :(得分:3)

您可以通过将 XmlReaderSettings.XmlResolver 属性设置为null来禁止XmlReader打开任何外部资源。

System.Xml.XmlReaderSettings xmlReaderSettings = new System.Xml.XmlReaderSettings ();
xmlReaderSettings.XmlResolver = null;
System.Xml.XmlReader xmlReader = System.Xml.XmlReader.Create(myUrl, xmlReaderSettings);

答案 2 :(得分:2)

当您的ResolveUri方法获取对-//W3C//ELEMENTS XHTML Images 1.0//EN之类的URI的“公共”形式的请求时,您的方法会抛出并等待以{{1}开头的后续类似网络的URI }?

我将公共URI解析为相应的http:// URI(然后在我的http://方法中拦截对GetEntity URI的请求),而不是抛出。

因此我永远不必扔掉,我认为这是正确的解决方案。


  

这是一种聪明的方法。你的字典有多大?我指向你的库只处理XHTML 1.0,并且只需要映射一个公共URI库。

我正在使用“模块化”的XHTML 1.1,所以我必须映射大约40个文件。

请注意框架的行为可能已经改变了!我有一个使用.NET Framework 2构建的库(包括我的XhtmlUrlResolver类),但根据应用程序(使用该库)是为.NET 2还是.NET 4构建而调用它会有所不同。

使用.NET 2,当我的ResolveUri方法总是只透明地委托给XmlUrlResolver时,它会:

  1. 要求ResolveUri公众提供DTD。
  2. 尝试从磁盘GetEntity DTD(抛出一个DirectoryNotFoundException)
  3. 尝试从http(我从本地资源提供)中获取DTD的DTD。
  4. 尝试从http(我从本地资源提供)
  5. 中获取每个其他文件的GetEntity

    使用.NET 4,每个资源都有一个额外的调用:

    • 向ResolveUri公开请求子资源(例如http://文件),我的实现刚刚委托给XmlUrlResolver
    • 向GetEntity询问子资源的'已解决'公开,这根本没有真正解决,只是添加了类似http的前缀(抛出WebException)

    抛出所有这些WebExceptions减慢了处理速度,这就是为什么我重新审视它以寻找修复。

    你的建议,我从ResolveUri抛出,解决了这个问题,我感谢你;但是不是投掷,而是从ResolveUri返回更优雅的东西(并且更快一点:减少40个例外)。

    这是我目前的源代码。

    *.mod
相关问题