Exit
  • Global community
    • Language:
      • Deutsch
      • English
      • Español
      • Français
      • Português
  • 日本語コミュニティ
  • 한국 커뮤니티
0

evaluateXPathExpression giving me problems

Community Beginner ,
Oct 20, 2017 Oct 20, 2017

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?

TOPICS
Scripting
1.8K
Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
People's Champ ,
Oct 20, 2017 Oct 20, 2017

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();

Capture d’écran 2017-10-20 à 23.07.20.png

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Beginner ,
Oct 23, 2017 Oct 23, 2017

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;

}

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Enthusiast ,
Oct 20, 2017 Oct 20, 2017

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

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Beginner ,
Oct 26, 2017 Oct 26, 2017

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!

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
People's Champ ,
Oct 26, 2017 Oct 26, 2017

Let's start over from the beginning what is the order you are expecting exactly ?

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Beginner ,
Oct 26, 2017 Oct 26, 2017

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"/>

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
People's Champ ,
Oct 26, 2017 Oct 26, 2017

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++;

}

Capture d’écran 2017-10-26 à 17.21.42.png

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Beginner ,
Oct 26, 2017 Oct 26, 2017

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.

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Contributor ,
Oct 26, 2017 Oct 26, 2017
LATEST

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

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
People's Champ ,
Oct 26, 2017 Oct 26, 2017

T_Stevens  a écrit

I could switch my initial search to "//bodyMatter//*[@newStory]"

Yes indeed. At some point you need to add some logic.

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines