30

似乎所有主流浏览器都实现了 DOMParser API,以便可以将 XML 解析为 DOM,然后使用 XPath、getElementsByTagName 等进行查询......

但是,检测解析错误似乎更棘手。 DOMParser.prototype.parseFromString总是返回一个有效的 DOM。发生解析错误时,返回的 DOM 包含一个<parsererror>元素,但在各个主流浏览器中略有不同。

示例 JavaScript:

xmlText = '<root xmlns="http://default" xmlns:other="http://other"><child><otherr:grandchild/></child></root>';
parser = new DOMParser();
dom = parser.parseFromString(xmlText, 'application/xml');
console.log((new XMLSerializer()).serializeToString(dom));

Opera 的结果:

DOM 的根是一个<parsererror>元素。

<?xml version="1.0"?><parsererror xmlns="http://www.mozilla.org/newlayout/xml/parsererror.xml">Error<sourcetext>Unknown source</sourcetext></parsererror>

结果在 Firefox 中:

DOM 的根是一个<parsererror>元素。

<?xml-stylesheet href="chrome://global/locale/intl.css" type="text/css"?>
<parsererror xmlns="http://www.mozilla.org/newlayout/xml/parsererror.xml">XML Parsing Error: prefix not bound to a namespace
Location: http://fiddle.jshell.net/_display/
Line Number 1, Column 64:<sourcetext>&lt;root xmlns="http://default" xmlns:other="http://other"&gt;&lt;child&gt;&lt;otherr:grandchild/&gt;&lt;/child&gt;&lt;/root&gt;
---------------------------------------------------------------^</sourcetext></parsererror>

Safari 中的结果:

<root>元素解析正确,但包含嵌套在与 Opera 和 Firefox元素<parsererror>不同的命名空间中。<parsererror>

<root xmlns="http://default" xmlns:other="http://other"><parsererror xmlns="http://www.w3.org/1999/xhtml" style="display: block; white-space: pre; border: 2px solid #c77; padding: 0 1em 0 1em; margin: 1em; background-color: #fdd; color: black"><h3>This page contains the following errors:</h3><div style="font-family:monospace;font-size:12px">error on line 1 at column 50: Namespace prefix otherr on grandchild is not defined
</div><h3>Below is a rendering of the page up to the first error.</h3></parsererror><child><otherr:grandchild/></child></root>

我是否缺少一种简单的跨浏览器检测 XML 文档中是否发生解析错误的方法?<parsererror>或者我必须为不同浏览器可能生成的每个可能元素查询 DOM ?

4

4 回答 4

23

这是我想出的最好的解决方案。

我尝试解析一个故意无效的 XML 字符串并观察结果<parsererror>元素的命名空间。然后,在解析实际 XML 时,我可以getElementsByTagNameNS用来检测相同类型的<parsererror>元素并抛出 Javascript Error

// My function that parses a string into an XML DOM, throwing an Error if XML parsing fails
function parseXml(xmlString) {
    var parser = new DOMParser();
    // attempt to parse the passed-in xml
    var dom = parser.parseFromString(xmlString, 'application/xml');
    if(isParseError(dom)) {
        throw new Error('Error parsing XML');
    }
    return dom;
}

function isParseError(parsedDocument) {
    // parser and parsererrorNS could be cached on startup for efficiency
    var parser = new DOMParser(),
        errorneousParse = parser.parseFromString('<', 'application/xml'),
        parsererrorNS = errorneousParse.getElementsByTagName("parsererror")[0].namespaceURI;

    if (parsererrorNS === 'http://www.w3.org/1999/xhtml') {
        // In PhantomJS the parseerror element doesn't seem to have a special namespace, so we are just guessing here :(
        return parsedDocument.getElementsByTagName("parsererror").length > 0;
    }

    return parsedDocument.getElementsByTagNameNS(parsererrorNS, 'parsererror').length > 0;
};

请注意,此解决方案不包括 Internet Explorer 所需的特殊情况。但是,在 IE 中事情要简单得多。XML 使用一种loadXML方法解析,如果解析成功或失败,则分别返回 true 或 false。有关示例,请参见http://www.w3schools.com/xml/xml_parser.asp 。

于 2012-07-24T02:19:13.597 回答
17

当我第一次来到这里时,我赞​​成原始答案(通过cspotcode),但是,它在 Firefox 中不起作用。由于生成的文档的结构,生成的命名空间始终为“null”。我做了一些研究(在这里查看代码)。这个想法是不使用

invalidXml.childNodes[0].namespaceURI

invalidXml.getElementsByTagName("parsererror")[0].namespaceURI

然后按照原始答案按名称空间选择“parsererror”元素。但是,如果您有一个有效的 XML 文档,其<parsererror>标签与浏览器使用的名称空间相同,那么您最终会收到误报。因此,这里有一个启发式方法来检查您的 XML 是否成功解析:

function tryParseXML(xmlString) {
    var parser = new DOMParser();
    var parsererrorNS = parser.parseFromString('INVALID', 'application/xml').getElementsByTagName("parsererror")[0].namespaceURI;
    var dom = parser.parseFromString(xmlString, 'application/xml');
    if(dom.getElementsByTagNameNS(parsererrorNS, 'parsererror').length > 0) {
        throw new Error('Error parsing XML');
    }
    return dom;
}

为什么不在 DOMParser 中实现异常?

在当前上下文中值得一提的有趣事情:如果您尝试使用 获取 XML 文件XMLHttpRequest,解析的 DOM 将存储在responseXML属性中,或者null,如果 XML 文件内容无效。不是一个例外,不是parsererror或另一个特定的指标。只是空。

于 2013-11-29T22:34:12.873 回答
1

回到 2022 年的这个问题,该方法的文档DOMParser.parseFromString()提供了一个更简单的解决方案:

const parser = new DOMParser();

const xmlString = "<warning>Beware of the missing closing tag";
const doc = parser.parseFromString(xmlString, "application/xml");
const errorNode = doc.querySelector('parsererror');
if (errorNode) {
  // parsing failed
} else {
  // parsing succeeded
}

虽然接受的答案对我有用,但使用该Document.querySelector()方法确实要简单得多,因为您不必确定元素namespaceURI的。parsererror

于 2022-01-23T17:06:48.077 回答
0

在当前浏览器中,当给定格式错误的 XML 时,DOMParser 似乎有两种可能的行为:

  1. 完全丢弃生成的文档 - 返回<parsererror>包含错误详细信息的文档。Firefox 和 Edge 似乎总是采用这种方法;Chrome 系列的浏览器在大多数情况下都会这样做。

  2. 返回结果文档,其中插入了一个额外的<parsererror>作为根元素的第一个子元素。Chrome 的解析器会在尽管在源 XML 中发现错误但仍能够生成根元素的情况下执行此操作。插入的<parsererror>可能有也可能没有命名空间。文档的其余部分似乎完好无损,包括注释等。请参阅xml_errors.cc — 搜索XMLErrors::InsertErrorMessageBlock.

对于(1),检测错误的方法是在源字符串中添加一个节点,对其进行解析,检查该节点是否存在于结果文档中,然后将其删除。据我所知,在不影响结果的情况下实现此目的的唯一方法是将处理指令或注释附加到源代码的末尾。

例子:

let key = `a`+Math.random().toString(32);

let doc = (new DOMParser).parseFromString(src+`<?${key}?>`, `application/xml`);

let lastNode = doc.lastChild;
if (!(lastNode instanceof ProcessingInstruction)
    || lastNode.target !== key
    || lastNode.data !== ``)
{
    /* the XML was malformed */
} else {
    /* the XML was well-formed */
    doc.removeChild(lastNode);
}

如果发生情况(2),上述技术将无法检测到错误,因此需要另一个步骤。

<parsererror>即使在源代码的不同位置发现多个错误,我们也可以利用仅插入一个这一事实。通过再次解析源字符串,此时附加语法错误,我们可以确保触发 (2) 行为,然后检查<parsererror>元素的数量是否发生了变化——如果没有,第一个parseFromString结果已经包含一个 true <parsererror>

例子:

let errCount = doc.documentElement.getElementsByTagName(`parsererror`).length;
if (errCount !== 0) {
    let doc2 = parser.parseFromString(src+`<?`, `application/xml`);
    if (doc2.documentElement.getElementsByTagName(`parsererror`).length === errCount) {
        /* the XML was malformed */
    }
}

我整理了一个测试页面来验证这种方法:https ://github.com/Cauterite/domparser-tests 。

它针对整个XML W3C 一致性测试套件进行测试,加上一些额外的样本,以确保它可以区分包含<parsererror>元素的文档和 DOMParser 发出的实际错误。只有少数测试用例被排除在外,因为它们包含无效的 unicode 序列。

需要明确的是,它只是测试结果是否与给XMLHttpRequest.responseXML定文档的结果相同。

您可以在https://cauterite.github.io/domparser-tests/index.html自己运行测试,但请注意它使用 ECMAScript 2018。

在撰写本文时,所有测试都通过了最新版本的 Firefox、Chrome、Safari 和 Android 上的 Firefox。基于 Edge 和 Presto 的 Opera 应该可以通过,因为它们的 DOMParsers 的行为看起来像 Firefox,而当前的 Opera 应该可以通过,因为它是 Chromium 的一个分支。


如果您能找到任何反例或可能的改进,请告诉我。

对于懒惰的人,这是完整的功能:

const tryParseXml = function(src) {
    /* returns an XMLDocument, or null if `src` is malformed */

    let key = `a`+Math.random().toString(32);

    let parser = new DOMParser;

    let doc = null;
    try {
        doc = parser.parseFromString(
            src+`<?${key}?>`, `application/xml`);
    } catch (_) {}

    if (!(doc instanceof XMLDocument)) {
        return null;
    }

    let lastNode = doc.lastChild;
    if (!(lastNode instanceof ProcessingInstruction)
        || lastNode.target !== key
        || lastNode.data !== ``)
    {
        return null;
    }

    doc.removeChild(lastNode);

    let errElemCount =
        doc.documentElement.getElementsByTagName(`parsererror`).length;
    if (errElemCount !== 0) {
        let errDoc = null;
        try {
            errDoc = parser.parseFromString(
                src+`<?`, `application/xml`);
        } catch (_) {}

        if (!(errDoc instanceof XMLDocument)
            || errDoc.documentElement.getElementsByTagName(`parsererror`).length
                === errElemCount)
        {
            return null;
        }
    }

    return doc;
}
于 2019-04-19T04:17:27.893 回答