解析包含未定义实体的XHTML文档

6

在使用Python编程时,如果我需要加载未定义实体的XHTML文档,我会创建一个解析器并更新实体字典(即nbsp):

import xml.etree.ElementTree as ET
parser = ET.XMLParser()
parser.entity['nbsp'] = ' '
tree = ET.parse(opener.open(url), parser=parser)

我尝试使用VB.Net将XHTML文档解析为Linq XDocument:

Dim x As XDocument = XDocument.Load(url)

出现了XmlException:

引用未声明的实体“nbsp”

搜索了一下,我没有找到任何更新实体表或使用简单方法来解析XHTML文档中未定义实体的示例。

如何解决这个看起来很简单的问题呢?


XDocument.Load 方法是否也会读取 DTD 中的实体表? - Mr Lister
默认情况下不行,我需要使用XmlReader并为XDocument启用DTDProcessing。但是我只需要向解析器添加此实体,而无需请求w3c或在本地存储XHTML的DTD。 - theta
3个回答

3
实体解析是由底层解析器完成的,这里使用的是标准的XmlReader(或XmlTextReader)。官方上,你应该在DTD中声明实体(请参见Oleg在此处的回答:Problem with XHTML entities),或将DTD动态加载到您的文档中。这里有一些示例,例如How do I resolve entities when loading into an XDocument?

您还可以创建一个hacky XmlTextReader派生类,当检测到实体时,基于字典返回Text节点,就像我在以下示例代码中演示的那样:

using (XmlTextReaderWithEntities reader = new XmlTextReaderWithEntities(MyXmlFile))
{
    reader.AddEntity("nbsp", "\u00A0");
    XDocument xdoc = XDocument.Load(reader);
}

...

public class XmlTextReaderWithEntities : XmlTextReader
{
    private string _nextEntity;
    private Dictionary<string, string> _entities = new Dictionary<string, string>();

    // NOTE: override other constructors for completeness
    public XmlTextReaderWithEntities(string path)
        : base(path)
    {
    }

    public void AddEntity(string entity, string value)
    {
        _entities[entity] = value;
    }

    public override bool Read()
    {
        if (_nextEntity != null)
            return true;

        return base.Read();
    }

    public override XmlNodeType NodeType
    {
        get
        {
            if (_nextEntity != null)
                return XmlNodeType.Text;

            return base.NodeType;
        }
    }

    public override string Value
    {
        get
        {
            if (_nextEntity != null)
            {
                string value = _nextEntity;
                _nextEntity = null;
                return value;
            }
            return base.Value;
        }
    }

    public override void ResolveEntity()
    {
        // if not found, return the string as is
        if (!_entities.TryGetValue(LocalName, out _nextEntity))
        {
            _nextEntity = "&" + LocalName + ";";
        }
        // NOTE: we don't use base here. Depends on the scenario
    }
}

这种方法适用于简单的情况,但为了完整性,您可能需要覆盖一些其他内容。

PS:抱歉它是用C#编写的,您需要适应VB.NET :)


感谢您的子类化解决方案,它对我非常有效 :) 我额外添加的一件事是在加载XDocument之前添加reader.XmlResolver = Nothing,因为没有它,XmlTextReader将从W3C拉取DTDs。 - theta

1
我还没有这样做过,但是您可以创建一个带有所需实体声明为 internalSubset 的 XmlParserContext 对象。将该上下文传递给 XmlTextReader 构造函数,并通过加载读取器来创建 XDocument 对象。MSDN中已经有一个看起来很简单的 VB 示例代码 example code snippet,用于使用预定义实体。

这看起来很简单,但似乎只适用于xmlFragments,并且我无法在来自url的XHTML文档中找到方法。 - theta

0

在这种情况下,我想你是在谈论网页,所以你可以使用Html Agility Pack来满足你的需求。

我使用XPath、元素和其他一些东西。它非常有用,可以搜索HTML页面等。

你可以在这里找到文档:htmlagilitypack


谢谢,但我不想使用特殊的包来解析XHTML,而且我也不想解析HTML DOM,而是XML。 - theta
你能给我解释一下 XML 是什么吗? - makemoney2010
它是一个不像HTML DOM那样昂贵的对象:http://en.wikipedia.org/wiki/XML_tree - theta
嗨Theta:D,我知道什么是XML,我回答错了问题:)对不起,有时我会用意大利语思考并按照意大利语法写作,抱歉。 - makemoney2010

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接