【问题标题】:How do I detect XML parsing errors when using Javascript's DOMParser in a cross-browser way?以跨浏览器方式使用 Javascript 的 DOMParser 时,如何检测 XML 解析错误?
【发布时间】:2012-07-18 19:08:53
【问题描述】:

似乎所有主流浏览器都实现了 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 的根是 &lt;parsererror&gt; 元素。

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

Firefox 中的结果:

DOM 的根是 &lt;parsererror&gt; 元素。

<?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 中的结果:

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

<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 文档中是否出现解析错误的方法?或者我必须为不同浏览器可能生成的每个可能的&lt;parsererror&gt; 元素查询 DOM?

【问题讨论】:

  • 你能在根DOM节点上调用.getElementsByTagName("parseerror"),如果返回的节点列表的长度大于零,就假设有错误吗?
  • 从技术上讲,我正在解析的 XML 文档可能包含 &lt;parsererror&gt; 元素,但仍然是完全有效的 XML(元素将来自不同的命名空间)所以我必须多次调用 .getElementsByTagNameNS(namespace, 'parsererror')来自每个浏览器的 namespace URI。
  • 嗯。至少可以说,HTML5 规范是零碎的。
  • 我注意到了这个mozilla bug,它指向了这个whatwg spec。我认为浏览器不使用异常是愚蠢的:正如您所写,我们可能不得不解析类似于作为错误返回的 XML 文档,并且无法判断它是否有效。完全解决问题的唯一方法是编写另一个解析器。
  • 看起来 Chrome 的功能与 Safari 相同。

标签: javascript xml


【解决方案1】:

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

我试图解析一个故意无效的 XML 字符串,并观察生成的 &lt;parsererror&gt; 元素的命名空间。然后,在解析实际 XML 时,我可以使用 getElementsByTagNameNS 来检测相同类型的 &lt;parsererror&gt; 元素并抛出 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。

【讨论】:

  • 我在下面的 this 和 Rast 的函数中得到以下信息:TypeError: Cannot read property 'namespaceURI' of undefined
【解决方案2】:

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

invalidXml.childNodes[0].namespaceURI

但是

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

然后按照原始答案按名称空间选择“parsererror”元素。但是,如果您在与浏览器使用的命名空间相同的命名空间中有一个带有&lt;parsererror&gt; 标记的有效 XML 文档,您最终会收到误报。 因此,这里有一个启发式方法来检查您的 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 中实现异常?

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

【讨论】:

  • PhantomJS 需要特殊处理。我试图在编辑第一个答案时反映这一点。
【解决方案3】:

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

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

  2. 返回结果文档,其中插入一个额外的&lt;parsererror&gt; 作为根元素的第一个子元素。 Chrome 的解析器会在尽管在源 XML 中发现错误但仍能够生成根元素的情况下执行此操作。插入的&lt;parsererror&gt; 可能有也可能没有命名空间。文档的其余部分似乎完好无损,包括 cmets 等。参考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),上述技术将无法检测到错误,因此需要另一个步骤。

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

例子:

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 Conformance Test Suite 进行测试,加上一些额外的样本,以确保它可以将包含&lt;parsererror&gt; 元素的文档与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;
}

【讨论】:

    猜你喜欢
    • 2011-12-18
    • 1970-01-01
    • 2022-11-21
    • 2015-05-16
    • 2012-06-09
    • 2015-05-16
    • 2017-05-20
    • 2013-02-09
    相关资源
    最近更新 更多