Search code examples
javascriptxmldomxmldocumentdomparser

How can I convert an XML file into Document object in Javascript?


I am working on a project where I need to read a long(50,000 lines) XML file. I decided to use the default Javascript Document or XMLDocument classes instead of a third party XML parser. And DOM tree structure of default Javascript XML parser works well for my design because of how I need to traverse to child and parent nodes freely.

I tried using XMLHttpRequest in the following way:

var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
        myFunction(this);
    }
};
xhttp.open("GET", "xmls/EXAMPLE.xml", true);
xhttp.send();

function myFunction(xml) {
    var xmlDoc = xml.responseXML;
    console.log(xmlDoc.getElementsByTagName("example")[0].getAttributeNode("name").nodeValue);
}

This works especially with xmlDoc object but after xhttp.send() the document is not accesible anymore. I want the document to stay available because I need to access different parts of it at different times according to client choices.

I have also looked into DOMParser but I am not sure if it is an efficient way to convert 50,000 lines of text into string and then turn it into a Document object.

Such as var xmlDoc = new DOMParser().parseFromString(xml, "application/xml");

I have been looking into some questions in stack overflow and MDN but haven't been able to figure out an easy way to do this.

EDIT: When I make the xmlDoc variable global, I get the error "Cannot read property 'getElementsByTagName' of undefined". The following is how I tried to make it global:

var xmlDoc;
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
        xmlDoc = this.responseXML;
        myFunction(this);
    }
};
xhttp.open("GET", "xmls/EXAMPLE.xml", true);
xhttp.send();

function myFunction(xml) {
    console.log(xmlDoc.getElementsByTagName("example")[0].getAttributeNode("name").nodeValue);
}

console.log(xmlDoc.getElementsByTagName("example")[0].getAttributeNode("name").nodeValue);

Here the console.log within "myFunction" works but the other one doesn't. I believe xmlDoc turns null after send().

Also this is literally everything I have in the javascript file, there is nothing else for the purposes of testing it.


Solution

  • The problem was with how I didn't understand how asynchronous functions work. Once I realized how the second line of console.log(xmlDoc.getElementsByTagName("example")[0].getAttributeNode("name").nodeValue); cannot be executed right away because the asynchronous function needs to be completed.

    I have switched to a promise implementation but then it made me question if it is a good design to let a global variable or a class variable be null until the async function is completed, and I wrote another question for that: What is a good design for global variables from Async functions in Javascript?

    However, currently what I am doing is:

    self.xmlDoc = null;
    
    this.promiseXML = new Promise(function(resolve, reject){
        var xhttp = new XMLHttpRequest();
        xhttp.open("GET", "xmls/EXAMPLE.xml", true);
        xhttp.onload = function(){
            if(xhttp.status == 200){
                self.xmlDoc = xhttp.responseXML;
                resolve();
            }else{
                reject(xhttp.statusText);
            }
        };
        xhttp.onerror = function(){
            reject(xhttp.statusText);
        }
        xhttp.send();
    })
    
    this.promiseXML.then(function(){
        console.log(self.xmlDoc.getElementsByTagName("parameter")[0].getAttributeNode("name").nodeValue);
    }).catch(function(err){
        console.log(err);
    });
    

    It may seem as if my problem of reusing xmlDoc is not necessarily solved because if you try console.log(self.xmlDoc.getElementsByTagName("example")[0].getAttributeNode("name").nodeValue); right after the promise, it still would be null. However, I will be doing all I need within the promise and once the applicaiton launches, it is for sure that self.xmlDoc is initiated.

    Therefore, if it needs to be used after the user makes a change, it will work without a problem. My original error was because of how I was trying to run my console.log before any time passes at all.

    For how my original code wasn't working, look into Daniel's comment and look into Asynchronous functions and callbacks. Learning about the event loop in javascript is also hlepful.

    For a good design approach, I am still not sure, but you can try to follow my other question or perhaps someone would help with a comment.

    PS: Using a promise is not necessary but it just seemed like a better practice for my situation.