flazzarini
flazzarini

Reputation: 8191

Reorder XML Elements in Extendscript Indesign

I want to reorder the following list of movies in such a way that movies of the version==3D should be placed before the ones in version==2D.

Input

<films>
  <film name="Foobar" version="2D"></film>
  <film name="Foobar" version="3D"></film>
  <film name="Foobaz" version="2D"></film>
  <film name="Foobaz" version="3D"></film>
</films>

Desired Output

<films>
  <film name="Foobar" version="3D"></film>
  <film name="Foobar" version="2D"></film>
  <film name="Foobaz" version="3D"></film>
  <film name="Foobaz" version="2D"></film>
</films>

I've fiddled around and ended up with the following code. Hopefully it understandable.

/***
 Extend Array prototype to have a indeOf function
 ***/
Array.prototype.indexOf = function(item) { 
    var index = 0, length = this.length;  
    for ( ; index < length; index++ ) {
        if ( this[index] == item )  
            return index;  
        }  
    return -1;  
};  


var xmlString = '\
<films>\
    <film name="Foobar" version="2D"></film>\
    <film name="Foobar" version="3D"></film>\
    <film name="Foobaz" version="2D"></film>\
    <film name="Foobaz" version="3D"></film>\
</films>';

var xml = new XML(xmlString);
var sortVersion = ['2D', '3D'];
var uniqueMovies = new Array();


for (var index = 0; index < xml.elements().length(); index++) {
    if (index == 0) continue;

    // Fetch meta data of previous movie
    var prevTitle = xml.elements()[index - 1].@name;
    var prevVersion = xml.elements()[index - 1].@version;
    var prevVersionIdx = sortVersion.indexOf(prevVersion);

    // Fetch meta data of current movie
    var curTitle = xml.elements()[index].@name;
    var curVersion = xml.elements()[index].@version;
    var curVersionIdx = sortVersion.indexOf(curVersion);

    // If both movie title matches verify which movie should be prioritized
    if (prevTitle == curTitle) {
      if (prevVersionIdx < curVersionIdx) { 
          // Movie prio movie before less prio movie
          xml.insertChildBefore(xml.elements()[index - 1], xml.elements()[index]);
          // And delete the next in index
          delete xml.elements()[index + 1];
      }
    }   
}

$.writeln("-----");
$.writeln("");
$.writeln(xml.elements().toString());
return xml

However when I run this script I end with the following result where nothing is changed at all although the both if condition are hit and the element on index[1] is added before index[0].

<films>
  <film name="Foobar" version="2D"/>
  <film name="Foobar" version="3D"/>
  <film name="Foobaz" version="2D"/>
  <film name="Foobaz" version="3D"/>
</films>

Does anyone have an idea what I am doing wrong here?

Upvotes: 1

Views: 528

Answers (1)

RobC
RobC

Reputation: 25032

Consider the following approach instead:

script.jsx

/**
 * Extend Array prototype to have a indexOf function
 */
Array.prototype.indexOf = function(item) {
    var index = 0, length = this.length;
    for ( ; index < length; index++ ) {
        if ( this[index] == item )
            return index;
        }
    return -1;
};

var xmlString = '\
<films>\
  <film name="Foobar" version="2D"></film>\
  <film name="Foobar" version="3D"></film>\
  <film name="Foobaz" version="2D"></film>\
  <film name="Foobaz" version="3D"></film>\
  <film name="Foo" version="3D"></film>\
  <film name="Quux" version="2D"></film>\
  <film name="Quux" version="3D"></film>\
</films>';

var xml = new XML(xmlString);
var sortVersion = ['2D', '3D'];

// 1. Create a temporary XML object with a  matching `films` root element.
var tempXml = new XML('<' + xml.name() + '></' + xml.name() + '>');

for (var index = 0, max = xml.elements().length(); index < max; index++) {

  // Fetch meta data of previous movie
  var prevTitle = String(xml.elements()[index - 1].@name);
  var prevVersion = xml.elements()[index - 1].@version;
  var prevVersionIdx = sortVersion.indexOf(prevVersion);

  // Fetch meta data of current movie
  var curTitle = String(xml.elements()[index].@name);
  var curVersion = xml.elements()[index].@version;
  var curVersionIdx = sortVersion.indexOf(curVersion);

  // 2. Insert each XML element node from the original XML Object into
  // the temporary XML Object and position (i.e. sort) as neccessary.
  if (prevTitle === curTitle && prevVersion < curVersion) {
    tempXml.insertChildBefore(tempXml.elements()[index -1], xml.elements()[index])
  } else {
    // There's nothing to change (i.e. sort) so insert it as-is .
    tempXml.insertChildBefore(tempXml.elements()[index], xml.elements()[index])
  }
}

// 3. Overwrite original XML object's children with temporary (sorted) XML objects children.
xml.setChildren(tempXml.children());

// 4. Delete temporary XML object.
tempXml = undefined;


// Testing...
$.writeln(xml)
$.writeln('-----------------')
$.writeln(xml.elements().length())
$.writeln('-----------------')

Explanation:

Instead of attempting to manipulate (i.e. sort) the original XML Object the gist above does the following instead:

  1. Creates another XML Object that contains a <films> root element only. This XML Object will be temporary.

  2. During each turn of the for loop we;

    • Insert a clone of each XML element node from the original XML Object into the temporary XML Object.
    • Position (i.e. sort) each XML element node as necessary.
  3. Overwrite the original XML objects child element nodes with child element nodes from the temporary/sorted XML Object.

  4. Finally we delete the temporary XML Object (i.e. set it to undefined) as it's no longer required.


Result

The example gist above includes the following source XML:

<films>
  <film name="Foobar" version="2D"></film>
  <film name="Foobar" version="3D"></film>
  <film name="Foobaz" version="2D"></film>
  <film name="Foobaz" version="3D"></film>
  <film name="Foo" version="3D"></film>
  <film name="Quux" version="2D"></film>
  <film name="Quux" version="3D"></film>
</films>

which will be transformed to the following:

<films>
  <film name="Foobar" version="3D"/>
  <film name="Foobar" version="2D"/>
  <film name="Foobaz" version="3D"/>
  <film name="Foobaz" version="2D"/>
  <film name="Foo" version="3D"/>
  <film name="Quux" version="3D"/>
  <film name="Quux" version="2D"/>
</films>

Note: For more complex XML structures and transformation requirements than the example provided in your question, I'd consider utilizing XSLT instead. Given your current requirement a template like this one will achieve your desired result.

Upvotes: 1

Related Questions