Search code examples
htmlxpathcustom-element

Injecting a custom element in HTML5 then retrieving its value using XPath comes back blank


I'm injecting an XML string generated from a web service, then trying to use XPath to query the attribute values using the following code.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <title>tox-js</title>
</head>
<body>
    <script>
    //
    // -----------------------------------------------
    // tox element
    class Tox extends HTMLElement
        {
        constructor(url)
            {
            super();
            fetch(url)
                .then((response)=>
                    {
                    console.log("status: "+response.status);
                    return response.text();
                    })
                .then((text)=>
                    {
                    console.log("text: "+text);
                    try
                        {
                        var dp = new DOMParser();
                        var xmlDOM = dp.parseFromString(text, "text/xml");
                        this.appendChild(xmlDOM.documentElement);
                        return true;
                        }
                    catch(err)
                        {
                        console.log("err: "+err.message);
                        return false;
                        }
                    })
                .then((ok)=>
                    {
                    if (ok)
                        {
                        try
                            {
                            var xpe = new XPathEvaluator();
                            var txt = xpe.evaluate("//tox-js/example/@timestamp",document,null,XPathResult.STRING_TYPE,null);
                            console.log("//tox-js/example/@timestamp: "+txt.stringValue);
                            txt = xpe.evaluate("//tox-js/example/@feedback",document,null,XPathResult.STRING_TYPE,null);
                            console.log("//tox-js/example/@feedback: "+txt.stringValue);
                            }
                        catch(err)
                            {
                            console.log("err: "+err.message);
                            }
                        }
                    else
                        console.log("not ok");
                    }
                    );
            }
        }
    //
    // -----------------------------------------------
    // register our element with the DOM
    customElements.define('tox-js',Tox);
    //
    // -----------------------------------------------
    // create an instance and add it to the body
    document.body.appendChild(new Tox('http://localhost:8080/tox/example.test.formatted?in_mask=YYYYMMDD'));
    // -----------------------------------------------
    //
    </script>
</body>
</html>

The result has the custom element injected.

<html lang="en">
    <head>...</head>
    <body>
        <script>...</script>
        <tox-js>
            <example timestamp="20180103142036" feedback="ok">20190103</example>
        </tox-js>
    </body>
<html>

The console log confirms the return status and XML, but the result of the XPath is blank.

[Log] status: 200 (toxElement3.html, line 20)
[Log] text: <example timestamp="20190103142036" feedback="ok">20190103</example> (toxElement3.html, line 25)
[Log] //tox-js/example/@timestamp:  (toxElement3.html, line 47)
[Log] //tox-js/example/@feedback:  (toxElement3.html, line 49)

Where have I gone wrong? This should not be a timing issue since I'm using .then to wait for the previous step.


Solution

  • Seems it is related to the namespaces. The following XPath works for me:

    //tox-js/*[local-name()='example']/@timestamp
    

    Check this answer: XPath Doesn't Work in Dynamic HTML-Document

    Also you can use document.createElement() or insertAdjacentHTML() to create element from text as described here: Creating a new DOM element from an HTML string using built-in DOM methods or Prototype

    In this case your XPath will work as expected.

    <html lang="en">
        <head></head>
        <body>
            <script>
                window.addEventListener('load', () => {
                    var text = `<example timestamp="20180103142036" feedback="ok">20190103</example>`;
                    var el = document.getElementsByTagName('tox-js')[0];
                    el.insertAdjacentHTML('afterbegin', text);
    
                    var xpe = new XPathEvaluator();
                    var txt = xpe.evaluate("//tox-js/example/@timestamp",document,null,XPathResult.STRING_TYPE,null);
                    console.log(`//tox-js/example/@timestamp: ${txt.stringValue}`);
                });
            </script>
            <tox-js>
            </tox-js>
        </body>
    <html>

    P.S. I can't explain why the problem happens when using DOMParser. Maybe there are different namespaces for document and DOMParser. So if somebody has more details, feel free to extend the answer.

    From the provided example...

    var dp = new DOMParser();
    var xmlDOM = dp.parseFromString(text, "text/xml");
    this.appendChild(xmlDOM.documentElement);
    

    ...becomes...

    this.insertAdjacentHTML('afterbegin', text);