0

Based on a value I input on the page, I want to retrieve the relevant XML data.

For example, if I input/select option "Michigan" from the datalist input field on my page, I want to find the node position of Michigan in the XML document. By knowing the node position, I can retrieve other information from the XML document corresponding to Michigan such as GDP and POPULATION.

I know I should change [1] to a variable such as [i], but how should I code the function?

var n = document.getElementById("myInputId");
n.addEventListener("keyup", function(event) {
  event.preventDefault();
  if (event.keyCode === 13) {
    document.getElementById("myButton").click();
  }
});

function loadXMLDoc() {
  var xmlhttp = new XMLHttpRequest();
  xmlhttp.onreadystatechange = function() {
    if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
      myFunction(xmlhttp);
    }
  };
  xmlhttp.open("GET", "state_data.xml", true);
  xmlhttp.send();
}

function myFunction(xml) {
  var x, i, xmlDoc;
  xmlDoc = xml.responseXML;
  x = xmlDoc.getElementsByTagName("UNIT")
  var s = xmlDoc.getElementsByTagName("STATE")[1].childNodes[0].nodeValue;
  document.getElementById("state").innerHTML = s;
  var gElem = xmlDoc.getElementsByTagName("GDP");
  var g = (gElem.length > 0) ? gElem[1].childNodes[0].nodeValue : "";
  document.getElementById("gdp").innerHTML = g;
  var pElem = xmlDoc.getElementsByTagName("POPULATION");
  var p = (pElem.length > 0) ? pElem[1].childNodes[0].nodeValue : "";
  document.getElementById("population").innerHTML = p;

  var a = n.value
  document.getElementById("inputValue").innerHTML = a;
  xmlDoc.UNITs.forEach(function(e, z) {
    var q = document.getElementById("inputValue").innerHTML;
    if (e.properties.id == q) {
      document.getElementById("nodePosition").innerHTML = z;
    }
  });
}
<body onload="loadXMLDoc()">
  <input list="myInput" id="myInputId" value="">
  <button id="myButton" onClick="loadXMLDoc()">submit</button>

  <p>input value: <span id="inputValue"></span></p>
  <p>XML tree node position of input value: <span id="nodePosition"></span></p>
  <p>State: <span id="state"></span></p>
  <p>GDP: <span id="gdp"></span></p>
  <p>Population: <span id="population"></span></p>

  <datalist id="myInput">
<option id="AL">Alabama</option>
<option id="CA">California</option>
<option id="MI">Michigan</option>
<option id="TX">Texas</option>
<option id="WI">Wisconsin</option>
</datalist>

Here's my XML:

<?xml version="1.0" encoding="UTF-8"?>
<STATE_DATA>
 <UNIT>
    <STATE>Wisconsin</STATE>
    <GDP>232,300,000,000</GDP>
    <POPULATION>5,800,000</POPULATION>
 </UNIT>
 <UNIT>
    <STATE>Alabama</STATE>
    <GDP>165,800,000,000</GDP>
    <POPULATION>4,900,000</POPULATION>
 </UNIT>
 <UNIT>
    <STATE>California</STATE>   
    <!-- Note: the GDP node for this unit is missing -->
    <POPULATION>39,600,000</POPULATION>
 </UNIT>
 <UNIT>
    <STATE>Texas</STATE>
    <GDP>1,600,000,000,000</GDP>
    <POPULATION>28,300,000</POPULATION>
 </UNIT>
 <UNIT>
    <STATE>Michigan</STATE>
    <GDP>382,000,000</GDP>
    <POPULATION>10,000,000</POPULATION>
 </UNIT>
</STATE_DATA>
user7637745
  • 965
  • 2
  • 14
  • 27
logoologist
  • 205
  • 3
  • 15

1 Answers1

1

Things are simplified somewhat if you resort to the xml handling functionality that comes with modern browsers. The rewrite centers around XPath expressions. A complete intro is beyond the scope of this answer, but in a nutshell it's a pattern language to retrieve content from an xml tree.

The xpath expressions are used to extract the relevant elements from the xml file.

The solution does not require any prior modifications to the xml ( contrary to the prior revision of this solution ).

The code sample is standalone - just save it as a local html file or serve it from a http server. To make it work, the xml data is stored in a script element serving as a data island.

Changing that design back to fetching the xml data with an ajax call should be straightforward..

Consider the code a proof of concept presenting the structure of an xpath-based approach.

<!DOCTYPE html>
<!--
    SO
    datalist / xml handling
    Q 51200490 (https://stackoverflow.com/questions/51200490/how-to-find-the-node-position-of-a-value-in-an-xml-tree/51201494)
    A 
-->
<html>
    <head>
        <title>SO sample</title>
        <script>
 // Setup of keypress event handler, default selection of xml data.
 function setupEH () {
    var n = document.getElementById("myInputId");
    n.addEventListener("keyup", function(event) {
        event.preventDefault();
        if (event.keyCode === 13) {
            document.getElementById("myButton").click();
        }
    });

    loadXMLDoc('Alabama'); // comment out this line if you want a vanilla UI after loading the html page.
}

// Load the xml document
function loadXMLDoc( statelabel ) {
    // The xml document is retrieved with the following steps:
    //      1. Obtain the (in-document) source as a DOM node.
    //      2. Extract textual content.
    //      3. Instantiate the xml parser (a browser built-in)
    //      4. Parse textual content into an xml document
    //
    //  When retrieving the xml document by means of ajax, these steps will be handled by the library for you - a parsed xml document will be available as a property or through calling a method.
    //
    let x_xmlisland = document.getElementById("template_xml");
    let s_xmlsource = x_xmlisland.textContent; 
    let parser = new DOMParser();
    let xmlDoc = parser.parseFromString(s_xmlsource, "application/xml");

    myFunction(xmlDoc, statelabel); // Actual work ...
}

// Processing the xml document 
function myFunction(xmlDoc, statelabel) {
    //    debugger; // uncomment to trace

    //
    //  Every bit of information is processed as follows:
    //      - Get the relevant xml subtree ( `UNIT` element of the selected state incl.descendants )
    //      - Extract the textual value.
    //      - Feed the textual value to the Html elements prsenting the result.
    //
    var xpr_current_unit  = xmlDoc.evaluate("/STATE_DATA/UNIT[./STATE[./text() = '"+statelabel+"']]",xmlDoc,null,XPathResult.ORDERED_NODE_ITERATOR_TYPE,null);
    var node_current_unit = xpr_current_unit.iterateNext();

    //
    //  The subsequent calls to xmlDoc.evaluate set the current UNIT element as their context node ('starting point'/'temporary root' for the xpath expression).
    //  The context node is referenced by '.' (dot) 
    //
    var xpr_s   = xmlDoc.evaluate("./STATE/text()",node_current_unit,null,XPathResult.ORDERED_ANY_TYPE,null);
    var node_s  = xpr_s.iterateNext();
    var s       = node_s.textContent
    document.getElementById("state").innerHTML = s;

    var xpr_g   = xmlDoc.evaluate("./GDP/text()",node_current_unit,null,XPathResult.ORDERED_ANY_TYPE,null);
    var node_g  = xpr_g.iterateNext();
    var g = "Unknown";
    if ( node_g !== null ) {
        g = node_g.textContent;
    }
    document.getElementById("gdp").innerHTML = g;

    var xpr_p   = xmlDoc.evaluate("./POPULATION/text()",node_current_unit,null,XPathResult.ORDERED_ANY_TYPE,null);
    var node_p  = xpr_p.iterateNext();
    var p = "Unknown";
    if ( node_p !== null ) {
        p = node_p.textContent;
    }
    document.getElementById("population").innerHTML = p;

    // cf. https://stackoverflow.com/a/3437009
    var xpr_u  = xmlDoc.evaluate("count(./preceding::UNIT)+1.",node_current_unit,null,XPathResult.ORDERED_ANY_TYPE,null);
    var n_ucount = xpr_u.numberValue;

    document.getElementById("inputValue").innerHTML = s;
    document.getElementById("nodePosition").innerHTML = n_ucount;
}

// Setup the submit click handler
function ehClick ( ) {
    let node_choice     = document.getElementById('myInputId');
    loadXMLDoc(node_choice.value);
}
        </script>
        <style>
        </style>
    </head>
    <body onload="setupEH()">
        <script id="template_xml" type="text/xml"><?xml version="1.0" encoding="UTF-8"?>
<STATE_DATA>
 <UNIT>
    <STATE>Wisconsin</STATE>
    <GDP>232,300,000,000</GDP>
    <POPULATION>5,800,000</POPULATION>
 </UNIT>
 <UNIT>
    <STATE>Alabama</STATE>
    <GDP>165,800,000,000</GDP>
    <POPULATION>4,900,000</POPULATION>
 </UNIT>
 <UNIT>
    <STATE>California</STATE>   
    <!-- Note: the GDP node for this unit is missing -->
    <POPULATION>39,600,000</POPULATION>
 </UNIT>
 <UNIT>
    <STATE>Texas</STATE>
    <GDP>1,600,000,000,000</GDP>
    <POPULATION>28,300,000</POPULATION>
 </UNIT>
 <UNIT>
    <STATE>Michigan</STATE>
    <GDP>382,000,000</GDP>
    <POPULATION>10,000,000</POPULATION>
 </UNIT>
</STATE_DATA>
        </script>
        <input list="myInput" id="myInputId" value="">
        <button id="myButton" onClick="ehClick()">submit</button>

        <p>input value: <span id="inputValue"></span></p>
        <p>XML tree node position of input value: <span id="nodePosition"></span></p>
        <p>State: <span id="state"></span></p>
        <p>GDP: <span id="gdp"></span></p>
        <p>Population: <span id="population"></span></p>

        <datalist id="myInput">
        <option id="AL">Alabama</option>
        <option id="CA">California</option>
        <option id="MI">Michigan</option>
        <option id="TX">Texas</option>
        <option id="WI">Wisconsin</option>
        </datalist>
    </body>
</html>

References

collapsar
  • 17,010
  • 4
  • 35
  • 61
  • Thanks for this thorough answer, though there's a caveat I should have been clear about. The XML file is located on an external server. Using XPath, will it be possible to modify the XML nonetheless? – logoologist Jul 06 '18 at 00:57
  • @logoologist XPath is a pattern language only.For manipulation of an XML document you need to resort to DOM methods or xslt (another xml technology, basically functional programming on an abstraction of xml trees). It is, however, possible to adjust the solution avoiding any change to the xml. I will complement the answer accordingly. – collapsar Jul 06 '18 at 01:05
  • What would you adjust in the solution to avoid changing the XML? – logoologist Jul 06 '18 at 01:16
  • I'm wondering if I can use something like this to fully index the XML: xmlDoc.forEach(function(e, z){ – logoologist Jul 06 '18 at 01:21
  • @logoologist I'm working on it rightnow :) Basically Xpath provides means to reference the textual content of an element ( in the abstract modelof an xml tree this corresponds to a node of their own). For your indexing idea, you enter xslt territory. It's definitely worth to learn about but may be overkill for the task at hand. – collapsar Jul 06 '18 at 01:29
  • @logoologist Done. – collapsar Jul 06 '18 at 01:43
  • Fantastic, this is the most thorough answer of all time. I'm studying it now and learning -- thank you! – logoologist Jul 06 '18 at 02:51
  • If I'm loading the XML from a document on the server, do I simply change the following? let x_xmlisland = document.getElementById("template_xml"); let s_xmlsource = x_xmlisland.textContent; – logoologist Jul 06 '18 at 04:59
  • were you suggesting I should use this code for an XML file? var xhttp = new XMLHttpRequest(); xhttp.onreadystatechange = function() { if (this.readyState == 4 && this.status == 200) { myFunction(this); } }; xhttp.open("GET", "state_data.xml", true); xhttp.send(); } – logoologist Jul 07 '18 at 02:28
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/174535/discussion-between-logoologist-and-collapsar). – logoologist Jul 07 '18 at 02:30