Copy link to clipboard
Copied
So here's the situation. I'm trying to automate an xml workflow so that when I get an XML file, I can run a script and it will place the XML into a template, map tags to styles, resize images, etc. I have most things working, but there's one problem, and it's the order of the xmlElements.
The XML has this basic structure:
<book>
<body>
<section>
<chapter id="01" newStory="A"/>
<chapter id="02" newStory="A"/>
<chapter id="worksheet-1" newStory="A"/>
<chapter id="03" newStory="A"/>
<chapter id="04" newStory="A"/>
<chapter id="worksheet-2" newStory="A"/>
</section>
<section>
<chapter id="05" newStory="A"/>
<chapter id="06" newStory="A"/>
<chapter id="worksheet-3" newStory="A"/>
<chapter id="07" newStory="A"/>
<chapter id="08" newStory="A"/>
<chapter id="worksheet-4" newStory="A"/>
</section>
<section>
<chapter id="09" newStory="A"/>
<chapter id="10" newStory="A"/>
<chapter id="worksheet-5" newStory="A"/>
<chapter id="11" newStory="A"/>
<chapter id="12" newStory="A"/>
<chapter id="worksheet-6" newStory="A"/>
</section>
</body>
<backMatter>
<section>
<chapter id="appx-1" newStory="B"/>
<chapter id="appx-2" newStory="B"/>
</section>
</backMatter>
</book>
The newStory attribute indicates when I need to start a new story, and the value tells me which master page should be applied to that chapter. I'm using
var myRoot = myDoc.xmlElements.item(0);
var stories = myRoot.evaluateXPathExpression("//*[@newStory]");
to get the xmlElements, then I place the xml into the document on a new page, then thread together text frames to fit the content. All of that works fine. The issue is that every section, except for the first and last get placed in reverse order. Basically, InDesign is reading the chapters like this:
<chapter id="01" newStory="A"/>
<chapter id="02" newStory="A"/>
<chapter id="worksheet-1" newStory="A"/>
<chapter id="03" newStory="A"/>
<chapter id="04" newStory="A"/>
<chapter id="worksheet-2" newStory="A"/>
<chapter id="worksheet-4" newStory="A"/>
<chapter id="08" newStory="A"/>
<chapter id="07" newStory="A"/>
<chapter id="worksheet-3" newStory="A"/>
<chapter id="06" newStory="A"/>
<chapter id="05" newStory="A"/>
<chapter id="worksheet-6" newStory="A"/>
<chapter id="12" newStory="A"/>
<chapter id="11" newStory="A"/>
<chapter id="worksheet-5" newStory="A"/>
<chapter id="10" newStory="A"/>
<chapter id="9" newStory="A"/>
<chapter id="appx-1" newStory="B"/>
<chapter id="appx-2" newStory="B"/>
I've considered trying to do a sort on my 'stories' array based on the id's, but I'm not sure how to incorporate the 'worksheet' and 'appx' values.
Does anyone have any idea's or suggestions on how I can make sure my elements are coming in in the proper order?
Copy link to clipboard
Copied
Hi,
A possible approach:
//Main routine
var main = function() {
var m = $.os[0]=="M",
mFilter = function(f){return /\.xml/.test(f.name); },
wFilter= "XML Files: *.xml",
f,
xo, stories, n = 0, arr = [], book = <book/>, tmpFile = File(Folder.temp+"/temp.xml" ),
doc = app.properties.activeDocument;
//Exit if no documents open
if ( !doc ) {
alert("You need an open document" );
return;
}
//Let's select some XML File
f = File.openDialog( "Please choose XML file", m? mFilter : wFilter, false );
if ( !f ) return;
//Let's load a XML Object out of this file
f.encoding = "UTF-8";
f.open('r');
xo = XML( f.read() );
f.close();
//Let's grab the stories from teh xml object
stories = xo.xpath( "//*[@newStory]");
n = stories.length()
if ( !n ) {
alert("The xml file doesn't look valid");
return;
}
//Let's tunr our XML List into a standard array so we can call sort
while ( n-- ) {
arr
= stories ; }
//Let's sort array
arr.sort (idSort);
var n = arr.length;
i = 0;
//Let's recreate a new XML object
while ( i<n ) {
book.appendChild (arr);
i++;
}
//Let's write this new XML object to a temp file
tmpFile.encoding = "UTF-8";
tmpFile.open ( "w" );
tmpFile.write ( book.toXMLString() );
tmpFile.close();
//Let's place this file (might need to set xml import options at this stage)
doc.importXML ( tmpFile );
//let's remove our file
tmpFile.remove();
}
var idSort = function ( a, b ) {
var idA = a.@id;
var idB = b.@id;
var numA = !isNaN (String(idA));
var numB = !isNaN (String(idB));
var textA, textB, reg = /(^.+)(\d+)$/;
//If both attributes are numbers, let's do a natural sorting
if ( numA && numB ) {
return Number(a.@id) > Number(b.@id);
}
//if a is a number bt b isn't then we want the b string after the a number everytime
else if ( numA && !numB ) {
return false;
}
//If a is a string and b a number, the a string is passed after the b number
else if ( !numA && numB ) {
return true;
}
else {
//This one is trickier
textA = String(a.@id);
textB = String(b.@id);
numA = Number ( textA.replace ( reg, "$2") );
numB = Number ( textB.replace ( reg, "$2") );
//If a & b are indexed strings (such as appx1, appx2
//We do a natural sort on the index
if ( textA.replace ( reg, "$1" ) == textB.replace ( reg, "$1" ) ) {
return numA > numB;
}
//otherwise we do a negative sorting so Worksheets come first to appx.
//But this logic may need to be reviewed after all.
else {
return textA < textB;
}
}
}
//Go tigers…
main();

Copy link to clipboard
Copied
Thanks for the response. Unfortunately, this won't work. First, I may have to preserve the xml (my company controls the text very tightly, so they usually compare the xml to the original before approving anything), so writing it to a new file isn't an option.
The other issue is that your sort places all the worksheets at the end, when I need them interspersed.
I found a solution that works for this specific example, but it's based around the idea that the first and last sections will always be right and the middle sections will always be reversed.
function getStories(myRoot) {
var parents = myRoot.evaluateXPathExpression("//*[child::*[@newStory]]");
var stories =[];
var myStories;
for (var i = 0; i < parents.length; i++) {
myStories = parents.evaluateXPathExpression("//*[@newStory]");
if ((i != 0) && (i != parents.length -1)) {
myStories = myStories.reverse();
}
for (var j = 0; j < myStories.length; j++) {
stories.push(myStories
}
}
return stories;
}
Copy link to clipboard
Copied
Use glue code.jsx
#include "C:/Program Files (x86)/Adobe/Adobe InDesign CS6/Scripts/XML Rules/glue code.jsx";
var ruleSet = new Array(new newStory);
__processRuleSet(doc.xmlElements.item(0),ruleSet);
function newStory()
{
this.xpath = "//*[@newStory]";
this.apply = function(element, processor)
{
alert(element.markupTag.name);
}
}
Regards,
Chinna
Copy link to clipboard
Copied
I've never been able to get the glue code to work. Even if I copy the full path directly into my #include statement, I get an error. That's why I've been using evaluateXPathExpression.
Does using glue code preserve the xml structure order?
Another fun wrinkle, I tried running my script again after relinking the xml, and now the order is completely different. Instead of the first and last sections being fine and everything else reversed, I'm getting the backmatter section first, then the body sections in the right order.
This is driving me nuts!
Copy link to clipboard
Copied
Let's start over from the beginning what is the order you are expecting exactly ?
Copy link to clipboard
Copied
I need to preserve the structure of the original xml and place the chapters into the document in that same order. What I need is the chapters placed into the document in the following order:
<chapter id="01" newStory="A"/>
<chapter id="02" newStory="A"/>
<chapter id="worksheet-1" newStory="A"/>
<chapter id="03" newStory="A"/>
<chapter id="04" newStory="A"/>
<chapter id="worksheet-2" newStory="A"/>
<chapter id="05" newStory="A"/>
<chapter id="06" newStory="A"/>
<chapter id="worksheet-3" newStory="A"/>
<chapter id="07" newStory="A"/>
<chapter id="08" newStory="A"/>
<chapter id="worksheet-4" newStory="A"/>
<chapter id="09" newStory="A"/>
<chapter id="10" newStory="A"/>
<chapter id="worksheet-5" newStory="A"/>
<chapter id="11" newStory="A"/>
<chapter id="12" newStory="A"/>
<chapter id="worksheet-6" newStory="A"/>
<chapter id="appx-1" newStory="B"/>
<chapter id="appx-2" newStory="B"/>
Copy link to clipboard
Copied
var doc = app.activeDocument;
var root = doc.xmlElements[0];
var xes = root.evaluateXPathExpression ( "//*[@newStory]") ;
var sortFn = function(a,b){
return a.id>b.id;
}
xes.sort(sortFn);
var i = 0; n = xes.length;
while ( i<n ) {
$.writeln( "i:"+xes.xmlAttributes.itemByName ( "id").value );
i++;
}

Copy link to clipboard
Copied
That's really close, but it puts the backmatter first.
That may not be a problem. I've been thinking of treating the backmatter separately anyway, so I could switch my initial search to "//bodyMatter//*[@newStory]"
I appreciate the help. I've been completely stumped on why InDesign has been pulling the xml elements out of order.
Copy link to clipboard
Copied
Hi,
it is not necessarily a better solution but maybe it helps to understand the issue. The order is the order of the xml element ids. Try it with a test document.
var _rootXMLElement = app.activeDocument.xmlElements[0];
var _allXMLElements = _rootXMLElement.evaluateXPathExpression("//*");
for(var i=0; i<_allXMLElements.length; i++) {
$.writeln(_allXMLElements.id + ": " + _allXMLElements.markupTag.name);
}
__reorderXMLElements(_rootXMLElement.xmlElements.everyItem().getElements());
var _allXMLElements = _rootXMLElement.evaluateXPathExpression("//*");
for(var i=0; i<_allXMLElements.length; i++) {
$.writeln(_allXMLElements.id + ": " + _allXMLElements.markupTag.name);
}
function __reorderXMLElements(_xmlElements) {
if(!_xmlElements || !(_xmlElements instanceof Array)) { return false; }
var _childXMLElements;
var _curXMLElement;
var i;
for(i=0; i<_xmlElements.length; i++) {
_curXMLElement = _xmlElements;
if(!_curXMLElement.isValid) { continue; }
_curXMLElement = _curXMLElement.move(LocationOptions.AFTER, _curXMLElement);
_childXMLElements = _curXMLElement.xmlElements.everyItem().getElements();
if(_childXMLElements.length > 0) {
__reorderXMLElements(_childXMLElements);
}
}
return _xmlElements;
} /* END function __reorderXMLElements */
Roland
Copy link to clipboard
Copied
T_Stevens a écrit
I could switch my initial search to "//bodyMatter//*[@newStory]"
Yes indeed. At some point you need to add some logic.
Get ready! An upgraded Adobe Community experience is coming in January.
Learn more