簡體   English   中英

以跨瀏覽器方式使用 Javascript 的 DOMParser 時如何檢測 XML 解析錯誤?

[英]How do I detect XML parsing errors when using Javascript's DOMParser in a cross-browser way?

似乎所有主流瀏覽器都實現了 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> 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?

這是我想出的最好的解決方案。

我嘗試解析一個故意無效的 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

當我第一次來到這里時,我贊​​成原始答案(通過cspotcode ),但是,它在 Firefox 中不起作用。 由於生成的文檔的結構,生成的命名空間始終為“空”。 我做了一些研究(在這里查看代碼)。 這個想法是使用 not

invalidXml.childNodes[0].namespaceURI

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

然后按原始答案中的命名空間選擇“parsererror”元素。 但是,如果您在與瀏覽器使用的命名空間相同的命名空間中有一個帶有<parsererror>標記的有效 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或其他特定指標。 只是空的。

在當前的瀏覽器中,當給定格式錯誤的 XML 時,DOMParser 似乎有兩種可能的行為:

  1. 完全丟棄生成的文檔 - 返回帶有錯誤詳細信息的<parsererror>文檔。 Firefox 和 Edge 似乎總是采用這種方法; 大多數情況下,Chrome 系列的瀏覽器都會執行此操作。

  2. 返回結果文檔,其中插入一個額外的<parsererror>作為根元素的第一個子元素。 盡管在源 XML 中發現錯誤,但 Chrome 的解析器會在能夠生成根元素的情況下執行此操作。 插入的<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結果已經包含了真<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。

在撰寫本文時,Android 上最新版本的 Firefox、Chrome、Safari 和 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;
}

回到 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()方法確實要簡單得多,因為您不必確定parsererror元素的namespaceURI

似乎所有主流瀏覽器都實現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>元件正確地解析,但是包含嵌套<parsererror>在不同的命名空間比Opera和Firefox的<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文檔中是否存在解析錯誤? 還是我必須查詢DOM以獲取不同瀏覽器可能生成的每個可能的<parsererror>元素?

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM