Ben Gross
Ben Gross

Reputation: 56

How to flatten an XML tree using JQuery?

My food development app requires an XML tree to be flattened. It sounds easy, but I have hit a snag. The algorithm requires that all the <stream>...</stream> nodes with a child <disposition>Break out overall</disposition> node to the top level <root>...</root> node.

Before:

<root>
    <stream>
        <name>Corn Oil</name>
        <quantity>0.3</quantity>
        <disposition>Break out overall</disposition>
    </stream>
    <stream>
        <name>Potatoes</name>
        <quantity>0.5</quantity>
        <disposition>Break out overall</disposition>
    </stream>
    <stream>
        <name>Seasoning</name>
        <quantity>0.1</quantity>
        <disposition>Break out overall</disposition>
        <isstream>true</isstream>
        <stream>
            <name>Salt</name>
            <quantity>0.02</quantity>
            <disposition>Break out overall</disposition>
        </stream>
        <stream>
            <name>Maltodextrin</name>
            <quantity>0.03</quantity>
            <disposition>Break out overall</disposition>
        </stream>
        <stream>
            <name>Spice Blend</name>
            <quantity>0.04</quantity>
            <disposition>Break out overall</disposition>
            <isstream>true</isstream>
            <stream>
                <name>Black Pepper</name>
                <quantity>0.02</quantity>
                <disposition>Break out overall</disposition>
            </stream>
            <stream>
                <name>Garlic</name>
                <quantity>0.02</quantity>
                <disposition>Break out overall</disposition>
            </stream>
            <stream>
                <name>Red Pepper</name>
                <quantity>0.02</quantity>
                <disposition>Break out overall</disposition>
            </stream>
        </stream>
    </stream>
</root>

Intended result after algorithm:

<root>
    <stream>
        <name>Corn Oil</name>
        <quantity>0.3</quantity>
        <disposition>Break out overall</disposition>
    </stream>
    <stream>
        <name>Potatoes</name>
        <quantity>0.5</quantity>
        <disposition>Break out overall</disposition>
    </stream>
    <stream>
        <name>Seasoning</name>
        <quantity>0.1</quantity>
        <disposition>Break out overall</disposition>
        <isstream>true</isstream>
    </stream>
    <stream>
        <name>Salt</name>
        <quantity>0.02</quantity>
        <disposition>Break out overall</disposition>
    </stream>
    <stream>
        <name>Maltodextrin</name>
        <quantity>0.03</quantity>
        <disposition>Break out overall</disposition>
    </stream>
    <stream>
        <name>Spice Blend</name>
        <quantity>0.04</quantity>
        <disposition>Break out overall</disposition>
        <isstream>true</isstream>
    </stream>
    <stream>
        <name>Black Pepper</name>
        <quantity>0.02</quantity>
        <disposition>Break out overall</disposition>
    </stream>
    <stream>
        <name>Garlic</name>
        <quantity>0.02</quantity>
        <disposition>Break out overall</disposition>
    </stream>
    <stream>
        <name>Red Pepper</name>
        <quantity>0.02</quantity>
        <disposition>Break out overall</disposition>
    </stream>
</root>

Actual result:

<root>
    <stream>
        <name>Corn Oil</name>
        <quantity>0.3</quantity>
        <disposition>Break out overall</disposition>
    </stream>
    <stream>
        <name>Potatoes</name>
        <quantity>0.5</quantity>
        <disposition>Break out overall</disposition>
    </stream>
    <stream>
        <name>Seasoning</name>
        <quantity>0.1</quantity>
        <disposition>Break out overall</disposition>
        <isstream>true</isstream>
        <stream>
            <name>Salt</name>
            <quantity>0.02</quantity>
            <disposition>Break out overall</disposition>
        </stream>
        <stream>
            <name>Maltodextrin</name>
            <quantity>0.03</quantity>
            <disposition>Break out overall</disposition>
        </stream>
        <stream>
            <name>Spice Blend</name>
            <quantity>0.04</quantity>
            <disposition>Break out overall</disposition>
            <isstream>true</isstream>
            <stream>
                <name>Black Pepper</name>
                <quantity>0.02</quantity>
                <disposition>Break out overall</disposition>
            </stream>
            <stream>
                <name>Garlic</name>
                <quantity>0.02</quantity>
                <disposition>Break out overall</disposition>
            </stream>
            <stream>
                <name>Red Pepper</name>
                <quantity>0.02</quantity>
                <disposition>Break out overall</disposition>
            </stream>
        </stream>
    </stream>
    <stream>
        <name>Salt</name>
        <quantity>0.02</quantity>
        <disposition>Break out overall</disposition>
    </stream>
    <stream>
        <name>Maltodextrin</name>
        <quantity>0.03</quantity>
        <disposition>Break out overall</disposition>
    </stream>
    <stream>
        <name>Spice Blend</name>
        <quantity>0.04</quantity>
        <disposition>Break out overall</disposition>
        <isstream>true</isstream>
        <stream>
            <name>Black Pepper</name>
            <quantity>0.02</quantity>
            <disposition>Break out overall</disposition>
        </stream>
        <stream>
            <name>Garlic</name>
            <quantity>0.02</quantity>
            <disposition>Break out overall</disposition>
        </stream>
        <stream>
            <name>Red Pepper</name>
            <quantity>0.02</quantity>
            <disposition>Break out overall</disposition>
        </stream>
    </stream>
    <stream>
        <name>Black Pepper</name>
        <quantity>0.02</quantity>
        <disposition>Break out overall</disposition>
    </stream>
    <stream>
        <name>Garlic</name>
        <quantity>0.02</quantity>
        <disposition>Break out overall</disposition>
    </stream>
    <stream>
        <name>Red Pepper</name>
        <quantity>0.02</quantity>
        <disposition>Break out overall</disposition>
    </stream>
</root>

The algorithm below and at JsFiddle works exactly as intended for the top level streams, Corn Oil, Potatoes, and Seasoning, dutifully removing each <stream>...</stream> node from under <root>...</root> node and appending it back under the <root>...</root> node. However, for all of the other nested <stream>...</stream> nodes in the formula (Spice Blend, Maltodextrin, Salt, Red Pepper, Garlic, Black Pepper), the $(this).remove() part of the algorithm does not work i.e. these streams are copied to the <root>...</root> node but the original node is not deleted.

var xmlAsStr = "<root><stream><name>Corn Oil</name><quantity>0.3</quantity><disposition>Break out overall</disposition></stream><stream><name>Potatoes</name><quantity>0.5</quantity><disposition>Break out overall</disposition></stream><stream><name>Seasoning</name><quantity>0.1</quantity><disposition>Break out overall</disposition><isstream>true</isstream><stream><name>Salt</name><quantity>0.02</quantity><disposition>Break out overall</disposition></stream><stream><name>Maltodextrin</name><quantity>0.03</quantity><disposition>Break out overall</disposition></stream><stream><name>Spice Blend</name><quantity>0.04</quantity><disposition>Break out overall</disposition><isstream>true</isstream><stream><name>Black Pepper</name><quantity>0.02</quantity><disposition>Break out overall</disposition></stream><stream><name>Garlic</name><quantity>0.02</quantity><disposition>Break out overall</disposition></stream><stream><name>Red Pepper</name><quantity>0.02</quantity><disposition>Break out overall</disposition></stream></stream></stream></root>";
    var xmlDoc = $.parseXML( xmlAsStr );
    var $xml = $( xmlDoc );
    
  $('stream',$xml).each(function(){
      if($(this).children('disposition').text()=="Break out overall" )
    {
       var thisHtml = $(this).html();
      $(this).remove();
      $xml.find('root').append('<stream>'+thisHtml+'</stream>');
    }
  });

In addition to $(this).remove(); I have tried: $xml.find('root').find(this).remove(); $xml.find(this).remove(); $(this,$xml).remove(); But nothing seems to work to remove these nested nodes from the XML tree. Any help or insight into how to accomplish the XML manipulation described above would be greatly appreciated. Thank you.

Upvotes: 0

Views: 220

Answers (1)

Ben Gross
Ben Gross

Reputation: 56

A bottom-up recursive algorithm did the trick to flatten the XML tree as intended. My original mistake was trying to rely on jQuery implicit iteration and traversing alone to solve this problem without explicit recursion. The solved code is below for your reference along with a working jsFiddle. Sorry to bug you, it is solved. Thank you so much.

$(function(){
    var xmlAsStr = "<root><stream><name>Corn Oil</name><quantity>0.3</quantity><disposition>Break out overall</disposition></stream><stream><name>Potatoes</name><quantity>0.5</quantity><disposition>Break out overall</disposition></stream><stream><name>Seasoning</name><quantity>0.1</quantity><disposition>Break out overall</disposition><isstream>true</isstream><stream><name>Salt</name><quantity>0.02</quantity><disposition>Break out overall</disposition></stream><stream><name>Maltodextrin</name><quantity>0.03</quantity><disposition>Break out overall</disposition></stream><stream><name>Spice Blend</name><quantity>0.04</quantity><disposition>Break out overall</disposition><isstream>true</isstream><stream><name>Black Pepper</name><quantity>0.02</quantity><disposition>Break out overall</disposition></stream><stream><name>Garlic</name><quantity>0.02</quantity><disposition>Break out overall</disposition></stream><stream><name>Red Pepper</name><quantity>0.02</quantity><disposition>Break out overall</disposition></stream></stream></stream></root>"; 
            var xmlDoc = $.parseXML( xmlAsStr );
            var $xml = $( xmlDoc ); 
            var $myResultingXml; 

            var streamNodeArray = []; 

            $('root',$xml).children('stream').each(function(){       
                streamNodeArray.push($(this)); 
            });

            var i = 0; 
            for(i=0;i<streamNodeArray.length;i++)
            {
                $myResultingXml = flattenXmlRecursive(streamNodeArray[i],$xml); 
            }

  var resultingXmlString = '<root>'+$myResultingXml.find("root").html()+'</root>'
  console.log(resultingXmlString );
});

function flattenXmlRecursive(xmlFragment,xmlDoc)
{
    var i = 0; 
    var streamNodeArray = []; 
    xmlFragment.children('stream').each(function(){
        streamNodeArray.push($(this)); 
    });

    for(i=0;i<streamNodeArray.length;i++)
    {
        flattenXmlRecursive(streamNodeArray[i],xmlDoc); 
    }

    if(xmlFragment.children('disposition').text()=='Break out overall')
    {
        xmlDoc.find(xmlFragment).remove(); 
        xmlDoc.find('root').append(xmlFragment); 
    }

    return xmlDoc; 
}

Upvotes: 1

Related Questions