21 Replies Latest reply on Dec 9, 2011 3:00 PM by John Hawkinson

    Indesign Scripting Object Model

    commnetsystems

      Hi,

       

      Is there some reference documentation that provides detailed information on the Indesign object model as exposed to the scripting interface (javascript)?

       

      I have done fair amount of scripting in Illustrator so I was looking for the Indesign equivalent of this document

      http://wwwimages.adobe.com/www.adobe.com/content/dam/Adobe/en/devnet/pdf/illustrator/scrip ting/illustrator_scripting_reference_javascript_cs5.pdf

       

      I looked at this document but it seems kind of sparse (compared to illustrator)

      http://wwwimages.adobe.com/www.adobe.com/content/dam/Adobe/en/products/indesign/pdfs/InDes ignCS5_ScriptingGuide_JS.pdf

       

      Obviously they are different applications but I was kind of hoping there would be more info for Indesign scripting.

       

      In particular I am looking for some insight on how to effectively walk or traverse the document structure of an indesign document using code.

       

      Finding specific things by type seems out of the picture becuase after doing object reflection on all the page items in an indesign document nothing seems to have a "type" property.

       

      In my illustrator scripts I was able to place stuff in illustrator layers, give arbitrary pageitems a name and then later go find and manipulate via script. Not as clear how to do this in an indesign context.

       

      The one advantage of this was that in illustrator I was able to use textframes to hold variable data and read later via scripts. The document becomes the database. And I could create any number of arbitrary "variables" inside the document outside the white board. WOndering how to do this kind of stuff with indesign so I can do template driven documents that are managed by scriptings and form UI that I create.

        • 1. Re: Indesign Scripting Object Model
          John Hawkinson Level 5

          Is there some reference documentation that provides detailed information on the Indesign object model as exposed to the scripting interface (javascript)?

          Jongware has produced a reference in CHM and HTML format from the ESTK's OMV XML, and it is invaluable:

          See http://jongware.com/idjshelp.html or the exploded HTML version at http://jongware.mit.edu/idcs5js/.

          In particular I am looking for some insight on how to effectively walk or traverse the document structure of an indesign document using code.

           

          Finding specific things by type seems out of the picture becuase after doing object reflection on all the page items in an indesign document nothing seems to have a "type" property.

          Typically one would iterate over the type of object you want, e.g. iterate over app.activeDocument.textFrames,

          and not over .pageItems.

           

          In my illustrator scripts I was able to place stuff in illustrator layers, give arbitrary pageitems a name and then later go find and manipulate via script. Not as clear how to do this in an indesign context.

          It's not clear to me how you are stuck. You can use .itemByName() to reference an item by its name, and also the script label may be useful for that. Or, of course, you can store the items (well, references to them) in an array or object of your own.

           

           

          The one advantage of this was that in illustrator I was able to use textframes to hold variable data and read later via scripts.

          You can use the script label property for that.

           

          Perhaps you could give us a much more concrete example of how you are stuck, because it's not really clear to me from your question...

          • 2. Re: Indesign Scripting Object Model
            commnetsystems Level 1

            Thanks John.

             

            This is helpful information. To be honest I am just starting to dip my toes into scripting for indesign. I was was curious about the object model because reading the documentation for illustrator gave a me a really firm grounding on how the internals of illustrator. What hoping for the same thing for indesign.

             

            Will look into the itemByName() function.

             

            Also, saw in another thread that there are insert and extract methods for the label property and the ability to create custom properties. I think this will allow me to do what I want to do.

            • 3. Re: Indesign Scripting Object Model
              commnetsystems Level 1

              I guess I have one question about substituing template values. Assume I have a text frame that has this tex set up as a template ready for modifcation:

               

               

              <frame>

              This is the content of my frame TEMPLATE_VARAIBLE_FOO and I can insert TEMPLATE_VARAIBLE_FOO2 inside the contents of the frame.

              </frame>

               

              Now I know I could create these text variables from an indesign template document and do a search and replace. But about after they have been substituted (via a script) how can I convenient edit them again (via a script)? eg something like this

               

              <frame>

              This is the content of my frame foo value and I can insert foo value 2 inside the contents of the frame.

              </frame>

               

              Do have to wrap each element inside a text frame and then set a specific label for each item? Basically I need a way to identify specific bits of content in a document by a consistent parameter or variable name so I can modify via script many times during the life of the document. And the data values would come from a scriptUI form I would be creating.

               

              Hope that makes sense. I want something will a stable label or property name I can go edit. Not regex or grep style search and replace.

              • 4. Re: Indesign Scripting Object Model
                John Hawkinson Level 5

                This is...not a common operation. InDesign does not give you an easy way to hang on to a reference to a range of text within a textframe so you can come back to it later -- because runs of characters are not objects (much less first-class objects).

                 

                I suppose you could do this with XML tags. You can apply an XML tag to a range of text (try this in the UI, first!), and see it in the Structure pane. You can then reference by the tag. Normally I would avoid XML at all costs, but it's probably the way to go here.

                 

                You could also keep track of the character offsets of the beginning and end of your substitutions. Or mark them with a character style. Those do not seem like good answers.

                • 5. Re: Indesign Scripting Object Model
                  [Jongware] Most Valuable Participant

                  commnetsystems wrote:

                  In particular I am looking for some insight on how to effectively walk or traverse the document structure of an indesign document using code.

                   

                  Yeah for that my version of the help should be handy as it shows the hierarchy of objects-within-objects.

                   

                  Well, there are always ugly hacks like inserting a note before and after the text to be replaced (not a footnote but the annotation one), but in your case you might want to look into XML.

                   

                  You can tag your variables with XML (this also requires some minimal XML framing around the entire story) and then read and/or change their contents from within a script almost painlessly.

                   

                  If your variable texts are quite short, you could try a shortcut and use .. InDesign variables! Mind you, their implementation is a bit on the shoddy side. You can insert text but it won't take local formatting and it won't break across lines.

                   

                  John Hawkinson wrote:

                  ... it is invaluable ...

                   

                  Thank you John!

                  • 6. Re: Indesign Scripting Object Model
                    commnetsystems Level 1

                    OK Looking at the XML documentation. Is it possible to access XML nodes by name?  For example assume the following XML

                     

                    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>

                    <Root>

                              <DocumentNumber>1234</DocumentNumber>

                              <DocumentDescription>Description Text</DocumentDescription>

                    </Root>

                     

                     

                    Now can I just want to access specific nodes via javascript but it doesn't work the way I expect

                     

                    var docRef = app.activeDocument;

                    var rootXMLElement = docRef.xmlElements.item(0);

                     

                     

                    // Show the contents of DocumentNumber e.g. 1234

                    alert(rootXMLElement.DocumentNumber.contents);

                     

                     

                    Is there a clear example somewhere that shows how this is done? Is it possible to do the way I am describing? All the documentation I can see seems to treat the xml as an array of elements accessed via index numbers.

                    • 7. Re: Indesign Scripting Object Model
                      John Hawkinson Level 5

                      OK Looking at the XML documentation. Is it possible to access XML nodes by name?

                       

                      <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
                      <Root>
                                <DocumentNumber>1234</DocumentNumber>
                      
                      

                      Err...

                       

                      Note that in InDesign XML DOM parlance, "DocumentNumber" is not the name of the XMLElement, it is the "markupTag."

                       

                      The first question to ask is, "Why would you want to?"

                       

                      At such time as you create the XML element, store a reference to it in a variable or an Array or an Object and come back to it later. Dispatching queries into the DOM to locate objects is never efficient anyhow.

                       

                      Back to your question. Could  you extract an XMLElement via it's markupTag? You could use .evaluateXPathExpression("DocumentNumber") evaluated on the root XML Element...but remember that in a normal XML schema, a tag need not be unique. For instance

                       

                      <Root>
                        <p>paragraph one</p>
                        <p>graf 2</p>
                      

                       

                      I suppose you can make guarantees that this is not a problem in your case...

                       

                      I suppose if you need to be able to do this across non-persistent runs of the script, that's a good reason.

                       

                      It might well be the case that iterating over the direct descendants of the root XMLElement is faster than evaluating the XPath expression though.

                      I suppose XPath is cleaner code, though.

                      • 8. Re: Indesign Scripting Object Model
                        commnetsystems Level 1

                        Thanks, John.

                         

                        "non-persistent runs of the script" is exactly the reason.

                         

                        The big idea here is that I am "persisting" important data directly inside the indesign document and I want scriptable access to it whenever I open the document so I can change in the future. Stuff like revision number, last modified date, etc. I am working on building a scriptUI form interface to make all the changes to a structured document in a consistent way. I have done something with illustrator and it works quite well for the technical puposes I have. Just trying to do similar things with indesign.

                         

                        The background reason is my company has hundreds of technical documents and I am trying to manage them with an effective document workflow using indesign and the adobe tool chain. A lot of this is custom so I am scripting specific solutions. Many documents are similar in form and structure but will be expressed as specific iterations. There is great convenience for me to have an scriptable interface overlay that provides structured and consistent access to parts of the document and little details in the boilerplate and specific areas of the documents. e.g I can type a document number and revision in one form and it automatically updates in half a dozen different locations in the document. This is the high level use case.

                        • 9. Re: Indesign Scripting Object Model
                          absqua Level 4

                          I recommend having a look at the XML Rules section in the scripting guide. I think that approach would be the best fit for what you're doing. The glue code.jsx Adobe supplies to work with them is weirdly implemented, but they provide a much faster (and, in my experience, more easily manageable) way to work with the xml structure of the document than either .evaluateXPathExpression() or recursive looping.

                           

                          If you do end up just looping over the elements, I would add to what John was saying by suggesting that you start by making an array you can loop over rather than looping over xmlElements collection objects. So start with

                           

                          var elements = doc.xmlElements[0].xmlElements.everyItem().getElements();
                          

                           

                          and loop over that. The difference in performance will be huge.

                           

                          Jeff

                          • 10. Re: Indesign Scripting Object Model
                            John Hawkinson Level 5

                            I recommend having a look at the XML Rules section in the scripting guide. I think that approach would be the best fit for what you're doing. The glue code.jsx Adobe supplies to work with them is weirdly implemented, but they provide a much faster (and, in my experience, more easily manageable) way to work with the xml structure of the document than either .evaluateXPathExpression() or recursive looping.

                            Ironically, I'd offer the opposite recommendation, to avoid the XML Rules section at pretty much all costs. It's terribly confusing, and I think the pain resulting from that confusion is rarely worth the efficiency gain.

                             

                            But a lot depends on whether we're talking about 10 XML elements or 10,000.

                            I was under the impression we were at 10.

                             

                            Using .everyItem().getElements() is certainly the way to go in any loop that's long and performance-critical, yes.

                            But most loops aren't, so I tend to avoid recommending it because of the confusion it engenders...

                            • 11. Re: Indesign Scripting Object Model
                              commnetsystems Level 1

                              I did play with the XML rules code examples a little bit. It was somewhat confusing but after looking at the javascript code closely I can see what they are trying to do. It is a little peculiar but I am a fairly experienced javascript programmer so it was not too difficult to make some sense of it.

                               

                              What I am really looking for is some clear code examples that allow me to modify the contents of a well structured indesign document. Also, trying to really grok the object model of indesign. I approached Ilustrator this way and learning that program from the point of view of its scripting interface was a very powerful training tool on understanding exactly was Illustrator can and cannot do. Hoping for the same experience with Indesign.

                               

                              I want to write get and set functions in my javscript files that reach into the document and modify snippets and sections of content as needed.

                               

                              Super performance speed of the script is not the biggest priority. What I really want is easy to under code/scripts (written in javascript) that allow me to consistently and effectively modify the contents of a document over time. I will likely be refining the scripts overtime as I improve the overall workflow.

                               

                              I have design control over the template and even the tags if I want to use in XML.

                               

                              I did look a little into IDML, but that looks like a whole can of worms. And I really want a solution that I could modify the underlying source content (XML, scripts, tags, etc) in a text editor like Textmate on OS X. And so far it seems the recommended solution to edit IDML content directly is with a $500 editor called Oxygen. That is a no go for me. I don't want my raw data locked up in proprietary formats as much as I can avoid that. Basic XML and text editor editable content and scripts is really valuable to me.

                               

                              Bottom line is I want to manipulate content with a text editor and javscript routines and use the power of indesign to layout images, style the presentation and publish to PDF (and eventually the web if that workflow works well).

                               

                              I have hundreds of manuals, catalogs and datasheets to produce and need an effective way to manage the content and publishing work flow. And spending lots of time manually tweaking individual text frames in individual indesign documents is not the most efficient use of my time if I can script some of the busy work.

                              • 12. Re: Indesign Scripting Object Model
                                John Hawkinson Level 5

                                While I'm not quite sure I understand your requirements [quite the opposite]...:

                                 

                                I did look a little into IDML, but that looks like a whole can of worms. And I really want a solution that I could modify the underlying source content (XML, scripts, tags, etc) in a text editor like Textmate on OS X. And so far it seems the recommended solution to edit IDML content directly is with a $500 editor called Oxygen. That is a no go for me. I don't want my raw data locked up in proprietary formats as much as I can avoid that. Basic XML and text editor editable content and scripts is really valuable to me.

                                Not at all! IDML is just a zipped folder of XML files. No need for proprietary anything.

                                • 13. Re: Indesign Scripting Object Model
                                  commnetsystems Level 1

                                  I tried fiddling with IDML files but seem to have some difficulties getting it to uncompress on OS X and edit the raw XML in my text editor of choice (Textmate). I guess I need to do unzip via the command line.

                                   

                                  Any recommendations on good IDML editing tools for OS X Lion, without spending an arm and a leg on Oxygen?

                                   

                                  And I do recognize in some ways the IDML format is more "open" and stable than the .indd which can change overtime with releases of Creative Suite.

                                  • 14. Re: Indesign Scripting Object Model
                                    John Hawkinson Level 5

                                    I always unzip on the command-line, but renaming the file to .zip should

                                    work fine too.

                                     

                                    I'm not so sure you really want to be editing IDML by hand. I just use

                                    my favorite text editor (emacs), but YMMV.

                                     

                                    "In some ways"? I can't think of any way that IDML is not more open

                                    and stable than INDD. Are we talking about the same thing here?

                                    There are hundreds of pages of documentation

                                    about IDML and it's format is self-describing. None of these are true

                                    of INDD (ok, I bet there are some internal adobe documents that run

                                    to hundreds of pages, but that's not the point...).

                                     

                                    Anyhow, I don't understand enough of your workflow to know that IDML

                                    is useful to you, but it certainly does have its uses.

                                    • 15. Re: Indesign Scripting Object Model
                                      absqua Level 4

                                      I think idml might be a good approach if you only had to change certain text contents. If you need to style text, manipulate graphic elements or add or remove page items, it gets a lot more involved.

                                       

                                      With apologies to John, here's a small example using xml rules:

                                       

                                      #include "/Applications/Adobe InDesign CS5/Scripts/XML Rules/glue code.jsx";
                                      function ChangeDocumentNumber(){
                                          this.name = "ChangeDocumentNumber";
                                          this.xpath = "//DocumentNumber";
                                          this.value = null;    
                                          this.apply = function(element, processor){
                                              if(this.value !== null){
                                                  element.contents = this.value;
                                              }
                                              return true;
                                          }
                                      }
                                      function ChangeDocumentDescription(){
                                          this.name = "ChangeDocumentDescription";
                                          this.xpath = "//DocumentDescription";
                                          this.value = null;
                                          this.apply = function(element, processor){
                                              if(this.value !== null){
                                                  element.contents = this.value;
                                              }
                                              return true;
                                          }
                                      }
                                      
                                      function main(){
                                          var doc = app.activeDocument;
                                          var changeNumber = new ChangeDocumentNumber();
                                          var changeDescription = new ChangeDocumentDescription();
                                          var ruleSet = [changeNumber, changeDescription];
                                      
                                          changeNumber.value = "5678";
                                          changeDescription.value = "This is the new description.";
                                      
                                          __processRuleSet(doc.xmlElements[0], ruleSet);
                                      }
                                      
                                      main();
                                      

                                       

                                      Clunky, and a lot of boilerplate for what we're doing here. But, if you have a lot of things to modify, it's quick, and it's easy to plug in new rules as you need them. You will need to develop a passing familiarity with xpath if you don't already have it, but you would have to have that if you were using idml or .evaluateXPathExpression() too.

                                       

                                      You could set the rule object .values in this example via a UI or file or whatever.

                                       

                                      Hope this helps.

                                      1 person found this helpful
                                      • 16. Re: Indesign Scripting Object Model
                                        commnetsystems Level 1

                                        Excellent answer. I see what you are doing there. You are adding .value properties to the functions and then using the this keyword to accces the value property inside the function. That makes sense and explains the "setter" type functionality. And I was already able to do a getters.

                                         

                                        Gotta love the mutability of javascript objects, yay for functions as first class objects! :-)

                                         

                                        Thanks for the concise example code. This gives me a good start.

                                        • 17. Re: Indesign Scripting Object Model
                                          John Hawkinson Level 5

                                          Err...I don't have time to post my recommended way to do this right now (will try to do so later tonight), but:

                                           

                                          Excellent answer. I see what you are doing there. You are adding .value properties to the functions and then using the this keyword to accces the value property inside the function. That makes sense and explains the "setter" type functionality. And I was already able to do a getters.

                                          That is not what is going on there. Those are not really functions -- they are Constructors (note Capital). When invoked by the new operator, they return an instance of the Object that they construct, which is bound to this within the scope of the Constructor.

                                           

                                          This is an example of what you get when Java programmers wh oare very used to classical (of or pertaining to "classes," not ancient/old) inheritance write Javascript. It is certainly a choice, but one that many find confusing, and it is especially problematic because the wrong thing happens if you ever call such a constructor without the new. See Classical Inheritance in Javascript by Douglas Grockford.

                                           

                                          So actually, this line (along with the Constructor I'm not going to quote).

                                           

                                           

                                          var changeNumber = new ChangeDocumentNumber();

                                           

                                           

                                          is really equivalent to:

                                          var changeNumber = {
                                              name: "ChangeDocumentNumber",
                                              xpath: "//DocumentNumber",
                                              value: null,    
                                              apply: function(element, processor){
                                                  if(this.value !== null){
                                                      element.contents = this.value;
                                                  }
                                                  return true;
                                              }
                                          };
                                          

                                          Personally I think this is a really confusing way to write it. If you're going to use these rules, I'd advocate a better helper function to create the rule objects, rather than using constuctors like that. (See forthcoming post, or you can search for some of mine on XML rules in the interim).

                                           

                                          But I think the important question remains: just how many properties do you have to manipulate inside your XML document? Ten or ten-thousand? What are the performance issues?

                                          • 18. Re: Indesign Scripting Object Model
                                            commnetsystems Level 1

                                            The nuance of the New keyword and the use of "apply" inside the object is not completely lost on me. However I am certainly probably guilty to misusing or causing confusion with my use of the word function.

                                             

                                            When I first looked at the examples in the documentation it was not clear how to "set" or update xml elements via the XML ruleset "Constructor" functions. As far as I know I have not come across an example of passing a parameter into these Constructor functions. But absqua's example makes some more sense. And the use of the new keyword and Constructor functions is different from what you find in typical web hacking javascript.

                                             

                                            I have read Crockford's book on javascript and the big take away I get from it is to understand three things

                                             

                                            Prototypical inheritance

                                            Virtually everything in javascript is an object and being a dynamic and loosely typed (therefore expressive) one has to forget the C++ and Java way of doing things. 

                                            Functional programming and closures are useful programming patterns that javascript offers in a unique way (the better parts of Lisp and Scheme).

                                             

                                            Looking forward to your example snippet though.

                                             

                                            And to answer the question about number of properties, probably talking max 100 or so. Certainly not thousands. I just want to have my own datastructure embedded in the indesign document that I can easily talk to via scripts. And this data needs to persist between instances of the javascript script's lifecycle. So it seems having some XML to parse is the solution. If that makes sense.

                                             

                                            If there was a way to deal with JSON data "hidden" within an indesign document I would gladly work with that. But I still need to change certain common content snippets inside the flow of the indesign document in a consistent way.

                                             

                                            When I look at the javascript in the Adobe documentation samples it is certainly "different" than what I would typically write for the browser. But then again the DOM is also different as well. And it is my understanding that Adobe "extends" javascript to do things you would never do in a browser, such as write to the file system. So I take it to be a different beast.

                                            • 19. Re: Indesign Scripting Object Model
                                              John Hawkinson Level 5

                                              commnet: Glad to hear you're clear on the new keyword. It's something that a lot of people are confused about.

                                              Also glad to hear you've read Crockford's book on this stuff. Honestly I think for most InDesign usage, it's a mistake to worry about any kind of inheritance...usually it's easier to ignore all that stuff.

                                              And to answer the question about number of properties, probably talking max 100 or so. Certainly not thousands. I just want to have my own datastructure embedded in the indesign document that I can easily talk to via scripts. And this data needs to persist between instances of the javascript script's lifecycle. So it seems having some XML to parse is the solution. If that makes sense.

                                               

                                              If there was a way to deal with JSON data "hidden" within an indesign document I would gladly work with that. But I still need to change certain common content snippets inside the flow of the indesign document in a consistent way.

                                              I think if you have max 100 properties, you should probably choose the methods that are clearest code, rather than most efficient. I suppose you might have noticable delay with 100, though, obviously try it and see. I'm confused but I thought you needed to have your data associated with text elements inside your document, such that it is visible to a user in the user interface. Hence the proposal of using XML tagged text inside a Text Frame. Did that requirement go away?

                                               

                                              If you just want to hide JSON in the document, then use document.insertLabel() of a string of JSON and be done with it.

                                              You can also use the JavaScript interpreter's E4X features, that give you much richer Javascript bindings for manipulating XML objects and files. Unfortunately the Javascript's XML bindings have nothing to do with the InDesign XML object model. So could, e.g., export the InDesign DOM XML into a file, read that into a Javascript XML object, manipulating with E4X functions, write it out to a file, and then re-import it into the DOM. Not really very convenient.

                                               

                                              I honestly don't know much about Javascript for browsers, so if you could be more concrete in your queries it would be easier to answer.

                                               

                                              Looking forward to your example snippet though.

                                              OK, so, in Jeff (absqua)'s example, you create a constructor for a changeNumber rule, you call the constructor to make a rule, add the rule to the ruleset, and change a value attribute of the rule, and then process the ruleset.

                                              This strikes me as...extremely goofy. For two reasons, first the ruleset creation function should be parametrized with the value you want to change, and second, that you go change the rule's attributes after you have added it to a ruleset. Also, while this works in this example, I don't think it's appropriate to have the xpath attribute hardcoded in the rule creation function -- it's much plausible that you want a function that constructs a rule that changes some arbitrary tag to some arbitrary value.

                                               

                                              So, anyhow, if you were going to stick with the constructor paradigm, then I would change from:

                                               

                                              var changeNumber = new ChangeDocumentNumber();
                                              var ruleSet = [changeNumber, changeDescription];
                                              changeNumber.value = "5678";
                                              __processRuleSet(doc.xmlElements[0], ruleSet);
                                              

                                               

                                              to:

                                               

                                              var changeNumber = new ChangeDocumentNumber();
                                              changeNumber.value = "5678";
                                              var ruleSet = [changeNumber, changeDescription];
                                              __processRuleSet(doc.xmlElements[0], ruleSet);
                                              

                                               

                                              But I don't like this, because it's not parametrized reasonably. I would want something like:

                                               

                                              var changeNumber = new ChangeValue("//DocumentNumber", "5678");
                                              var changeDescription = new ChangeValue("//DocumentDescription", "New descr.");
                                              var ruleSet = [ changeNumber, changeDescription ];
                                              

                                               

                                              But because of my disdain for classical inheritance in JavaScript, I would instead write it as:

                                               

                                              function changeValue(xpath, newValue){
                                                return {
                                                  name: "ChangeValue",
                                                  xpath: xpath,
                                                  apply: function(Element, processor) {
                                                    if (newValue) {
                                                      element.contents = newValue;
                                                    }
                                                    return true;
                                                  }
                                                };
                                              }
                                              var changeNumber = changeValue("//DocumentNumber", "5678");
                                              var changeDescription = changeValue("//DocumentDescription", "New descr.");
                                              

                                               

                                              Because I think that a lot clearer about what is going on without introducing confusion.

                                              Edit: removed "this.value" from the above and just used newValue directly.

                                               

                                              But if you're defining a lot of rules with different apply functions, this is a very cumbersome strategy. You have all this boilerplate for each function that is basically the same but the apply function is different. This is leads to a lot of wasted code duplications. Why would your apply function be different? Well, that would usually be if you are writing functions that move around XML elements within the document hierarchy, or other things that are not simply changing the value of an element.

                                               

                                              I would much prefer to write this as:

                                               

                                              var changeRule = makeRule("change",
                                                function(element, ruleProcessor, newValue) {
                                                  element.contents = newValue;
                                                });
                                              var changeNumber = changeRule("//DocumentNumber", "5678");
                                              var changeDescription = changeRule("//DocumentDescription", "New descr.");
                                              var ruleSet = [ changeNumber, changeDescription ];
                                              

                                               

                                              This kind of syntax makes it much more compact to create arbitrary rules with different apply functions. In retrospect, the makeRule() function is named wrong, because it returns a function that makes a rule. Perhaps it'd be more reasonable if it was called makeRuleMaker().

                                               

                                              Anyhow, the downside is that the makeRule() function is kind of heinous. I wrote this in March in Re: How to shift content with in cell in xml rules table. But:

                                               

                                               

                                              //// XML rule functions
                                              //
                                              // Adobe's sample for how to define these is HORRIBLE.
                                              // Let's fix several of these problems.
                                              //
                                              // First, a handy object to make clearer the return values
                                              // of XML rules:
                                               
                                              var XMLmm = { stopProcessing: true, continueProcessing: false}; 
                                               
                                              // Adobe suggest defining rules constructors like this:
                                              //
                                              //   function RuleName() {
                                              //       this.name = "RuleNameAsString";
                                              //       this.xpath = "ValidXPathSpecifier";
                                              //       this.apply = function (element, ruleSet, ruleProcessor){
                                              //       //Do something here.
                                              //       //Return true to stop further processing of the XML element
                                              //       return true;
                                              //       }; // end of Apply function
                                              //   }
                                              //
                                              // And then creating a ruleset like this:
                                              // 
                                              //   var myRuleSet = new Array (new RuleName, new anotherRuleName);
                                              // 
                                              // That syntax is ugly and, and is especially bad if
                                              // you need to parametrize the rule parameters, which is the only
                                              // reasonable approach to writing reasonable rules. Such as:
                                              // 
                                              //   function addNode(xpath, parent, tag) {
                                              //       this.name = "addNode";
                                              //       this.xpath = xpath;
                                              //       this.apply = function (element, ruleProcessor) {
                                              //           parent.xmlElements.add(tag);
                                              //           return XMLmm.stopProcessing;
                                              //       }
                                              //   }
                                              //
                                              // and then creating a ruleset like:
                                              //
                                              //   rule = new Array (new addNode("//p", someTag));
                                              //
                                              // So instead we introduce a makeRule function, that
                                              // allows us to leave behind all the crud. So then we can write:
                                              //
                                              // 
                                              // addNode = makeRule("addNode",
                                              // function(element, ruleProcessor, parent, tag) {
                                              //     parent.xmlElements.add(tag);
                                              //     return XMLmm.stopProcessing;
                                              // });
                                              //
                                              // and use:
                                              //
                                              // rule = [ addNode("//p", someTag ];
                                              //
                                              //
                                               
                                              function makeRule(name, f) {
                                                  return function(xpath) {
                                                      var
                                                          //xpath=arguments[0],
                                                             // "arguments" isn't a real array, but we can use
                                                             // Array.prototype.slice on it instead...
                                                             args=Array.prototype.slice.apply(arguments, [1]);
                                                      return {
                                                          name: name,
                                                          xpath: xpath,
                                                          apply: function(element, ruleProcessor) {
                                                                  // Slice is necessary to make a copy so we don't
                                                                  // affect future calls.
                                                              var moreargs = args.slice(0);
                                                              moreargs.splice(0, 0, element, ruleProcessor);
                                               
                                                              return f.apply(f,moreargs);
                                                          }
                                                      };
                                                  };
                                              }
                                              1 person found this helpful
                                              • 20. Re: Indesign Scripting Object Model
                                                commnetsystems Level 1

                                                Thanks John.

                                                 

                                                Great example code. This is much more obvious and feels like "normal" javascript. Agreed with the parameterization feature of this implementation. That was one aspect I was wondering about and your example clearly shows that. It is correct in that making the more general case is useful because I wouldn't necesarily know ahead of time which xml path I would want to manipulate. So your example is much more flexible. And I agree making functions as generic as possible is always fruitful effort. One can never quite anticipate the peculiar edge cases one will want reuse the code with. :-)

                                                 

                                                These generic utility type functions I tend keep in a library folder so I can share them between specific scripts.

                                                 

                                                And to be clear, yes, I do need to edit and manipulate content directly in the document. So reaching into the document's content structure is important. But if using JSON behind the scenes is possible that gives me a reliable datastore to interface with and more robust in case a user accidently edits or removes some important XML or content in the document. I would much rather have the script complain "can't find content x" and then in my interface allow the user to add back into the document programmatically via script.

                                                 

                                                Also, performance is not the highest priority. Even if it is a sluggish script it will still be significantly faster than making these kinds of edits manually. And more importantly less error prone, and more consistent. The consistency is the most important feature. Allows for future editability of the documents. Let computers do the tedious work and humans do the "creative" work.

                                                 

                                                I guess I would have to be concerned about script timeouts if it gets too sluggish. Do you know what the default timeouts are for javascript/extendscript in the Adobe Creative Suite is? In the browser it is typically seconds, and rarely more than 20 or 30 seconds. I presume in the Adobe context you can let the script run longer.

                                                 

                                                I would be fine letting the script run for minutes if need be. Especially for the longer procedures. In my illustrator scripts I have a whole "publishing" routine that takes a single illustrator document and writes out to the file system in pre defined locations multiple permutations of the file. This automation is very handy because we do a lot of nameplate label designs that have to be routed to a production and manufacturing process as well as design review. And this automation is very handy and a huge time saver, not to mention very consistent and easy to review the output of our design team.

                                                • 21. Re: Indesign Scripting Object Model
                                                  John Hawkinson Level 5

                                                  And to be clear, yes, I do need to edit and manipulate content directly in the document. So reaching into the document's content structure is important. But if using JSON behind the scenes is possible that gives me a reliable datastore to interface with and more robust in case a user accidently edits or removes some important XML or content in the document. I would much rather have the script complain "can't find content x" and then in my interface allow the user to add back into the document programmatically via script.

                                                  Ah, OK. I was under the impression you wanted two-way manipulation, that is, to allow the user to edit the content in the document, and then have the script see the user's changes. It sounds like maybe that's not critical. (Though you can add items to the XML structure of the document that are not actually page items, and then the user could manipulate them in the XML structure pane, in a sort of, "only touch this if you know what you're doing" kind of way.)

                                                   

                                                  I guess I would have to be concerned about script timeouts if it gets too sluggish. Do you know what the default timeouts are for javascript/extendscript in the Adobe Creative Suite is? In the browser it is typically seconds, and rarely more than 20 or 30 seconds. I presume in the Adobe context you can let the script run longer.

                                                  Not an issue. I've written scripts that run for days...