Anzeige
Tutorialbeschreibung

3. Teil: XML & InDesign Scripting is the key

3. Teil: XML & InDesign Scripting is the key

Dieses Tutorial soll euch zeigen, wie man mithilfe von JavaScript (InDesignskript) und XSL-Transformation XML-Daten in InDesign automatisch verarbeiten kann.

Im dritten Teil werden wir das Skript erweitern und alle XML-Daten in unserer InDesign-Datei automatisch platzieren. Wir werden mit dem Skript in der Lage sein, unser Produktbild entsprechend zu platzieren und neue Seiten anzulegen.


Enthaltene Arbeitsdateien

Aus Teil 2:

• tutorial_xml.idml
• zweite_daten.xml
• zweite.xsl
• Bilddatei
• second_step.jsx (ab CS6)
• alternative_second_step.jsx (ab CS4)

Neu

• final. jsx   (ab CS6)
• final_xpath.jsx (ab CS6)
• Skripte für CS4 /CS5

In diesem Teil werden wir uns hauptsächlich wieder mit dem Indesignskript befassen.

Vorerst aber eine kurze Checkliste, die ihr durchgehen solltet, bevor wir starten.

1. Ihr habt den Dateipfad für das Bild in der Datei zweite.xsl angepasst: fast am Ende <xsl:element name="bild"><xsl:attribute name="href">file:///f:/bilder/<xsl:value-of select="." /></xsl:attribute>

2. In eurer Datei tutorial_xml.indd befinden sich keine importierten XML-Daten und die Datei beinhaltet eine Titelseite und eine linke Seite. Die linke Seite ist leer (ausgenommen Felder aus Musterseite).

3. (Nur CS6 und höher): Eure InDesign-Datei enthält das Objektformat txt_feld, das wir in Teil 2 angelegt haben, um die Textfeldgröße automatisch an den Inhalt anzupassen.

Achtung: In Teil 2 habe ich erwähnt, dass es die Option zur automatischen Textfeldgröße erst ab CS6 gibt. Der Übersichtlichkeit zuliebe werde ich im Texttutorial nur die Skripte für CS6 posten. Die Skripte für CS4/CS5 sind in den Arbeitsdateien enthalten. Diese beinhalten nur eine zusätzliche Funktion zur Berechnung der Textfeldgröße. Ansonsten sind die Skripte nahezu identisch.

 
Wir öffnen unsere tutorial.indd. Importiert die zweite_Daten.xml und verwendet zweite.xsl

Startet danach unser second_step.jsx.

Das Ergebnis + Strukturansicht sollte jetzt entsprechend aussehen.

Bilder



 
Wir wollen nun die folgenden Fehler lösen, indem wir unser Skript erweitern.

1. Bild ist in Zeile
2. Nur ein XML-Element wurde platziert
3. Die Tabelle am Ende ist falsch positioniert / bzw. soll in ein eigenes Textfeld

Als Erstes kümmern wir uns um das Bild.

Hierzu speichern wir sicherheitshalber unser Skript als final.jsx ab. Mit diesem werden wir nun weiterarbeiten und es wie folgt erweitern.

final.jsx

var myDocument = app.documents.item(0);
var sitenumber = 1;
 
var inhalt= myDocument.xmlElements.item(0).xmlElements.item(0);
  
 set_textfeld(inhalt);
   
  function set_textfeld(inhalt){
    var myPage = myDocument.pages.item(sitenumber);
    var myTextFrame = myPage.textFrames.add();
    myTextFrame.appliedObjectStyle = myDocument.objectStyles.itemByName("txt_feld");
    myTextFrame.sendToBack();

    inhalt.placeXML(myTextFrame);
     
    myTextFrame.geometricBounds = myGetBounds();
    solve_image(myTextFrame);

    return
   }
 
function myGetBounds( ){
    myPage = myDocument.pages.item(sitenumber); 
    if(myPage.side == PageSideOptions.rightHand ){
          var myX1 = 227; var myX2 = 350;
    } else {
        var myX1 = 70; var myX2 = 193;
            }   
         
    var myY1 = 40;
    var myY2 = myY1+80;
    return [myY1, myX1, myY2, myX2];
}

function   solve_image(myTextFrame){
        if (myTextFrame.rectangles.length== 1){ 
              var x = myTextFrame.rectangles[0];
               x. geometricBounds = [myTextFrame.geometricBounds[0], 17, myTextFrame.geometricBounds[0]+40, 65];
               x.appliedObjectStyle = myDocument.objectStyles.itemByName("grafik"); 
        }
 }

Startet das Skript:

Bilder



Mit solve_image(myTextFrame); rufen wir die Funktion auf, die unser Bild-Problem beheben soll.

InDesign platziert von Haus aus die Bilddatei in ein verankertes Objekt.

Wir überprüfen, ob unser Textfeld ein verankertes Objekt beinhaltet

if (myTextFrame.rectangles.length== 1)

(Anmerkung: Da ich hier nur mit einer Bilddatei gearbeitet habe, funktioniert ==1, ansonsten eben >=1)

Falls ja, wird dem ersten verankerten Objekt myTextFrame.rectangles[0]; die Position zugewiesen.

 [myTextFrame.geometricBounds[0], 17, myTextFrame.geometricBounds[0]+40, 65]

[Obere Linie meines Textfeldes, 17, obere Linie meines Textfeldes meines Textfeldes +40, 65]

Tatsächlich macht uns hier InDesign einen Strich durch die Rechnung. Die vertikale sowie die horizontale Positionierung werden nicht richtig umgesetzt, da wohl der Bezugspunkt des verankerten Objektes nicht oder falsch definiert ist. Die Breite und die Höhe werden glücklicherweise angepasst.

Um eine korrekte Positionierung sicherzustellen und unser Bild proportional anzupassen, müssen wir dem Ganzen noch ein Objektformat zuweisen.

x.appliedObjectStyle = myDocument.objectStyles.itemByName("grafik");

Das Objektformat "grafik" ist schon in der InDesign-Datei enthalten. Bei der Erstellung des Objektformates muss man ein wenig mit den Werten bei Optionen für das verankerte Objekt spielen, um eine vernünftige Positionierung für die rechte und linke Seite zu erhalten.

Das Bild-Problem ist gelöst und wir wollen nun eine Schleife erstellen, um alle Daten zu platzieren.

Löscht als Erstes das platzierte Textfeld aus der tutorial_xml.indd. Die importierten XML-Daten bleiben weiterhin in eurer InDesign-Datei erhalten (siehe Strukturansicht).

Erinnern wir uns an den ersten Teil des Tutorials - dort haben wir gelernt, wie wir die Anzahl der Elemente auslesen.

alert (myDocument.xmlElements.itemByName("Bekleidung").xmlElements.length);

In unserem Fall ist es jetzt nicht mehr (Bekleidung), sondern "root"

Dann müssen wir eine Schleife starten, welche die Funktion set_textfeld() entsprechend oft startet, wobei sich natürlich der Inhalt ändern soll.

Und nicht zu vergessen: Ich möchte, dass die Textfelder nacheinander platziert werden.

var myDocument = app.documents.item(0);
var sitenumber = 1;
var u = 0
 
main();
function main() {
    var element_anzah = myDocument.xmlElements.itemByName("root").xmlElements.length;
    for ( var zaehl = 0; zaehl  < element_anzah; zaehl++) {
        lap()
        }
} 
 
 function lap () {
 var inhalt= myDocument.xmlElements.item(0).xmlElements.item(u);    
 set_textfeld(inhalt);
  u=u+1;
  }
   
  function set_textfeld(inhalt){
    var myPage = myDocument.pages.item(sitenumber);
    var myTextFrame = myPage.textFrames.add();
    myTextFrame.appliedObjectStyle = myDocument.objectStyles.itemByName("txt_feld");
    myTextFrame.sendToBack();

    inhalt.placeXML(myTextFrame);
     
    myTextFrame.geometricBounds = myGetBounds();
    solve_image(myTextFrame);

    return
   }
 
function myGetBounds( ){
    mypage = myDocument.pages.item(sitenumber);  
    if(mypage.side == PageSideOptions.rightHand ){
          var myX1 = 227; var myX2 = 350;
    } else {
        var myX1 = 70; var myX2 = 193; 
            }        
    if (mypage.textFrames.length > 1){
        var myY1 = mypage.textFrames[mypage.textFrames.length -2].geometricBounds[2]+15;
      } else{
        var myY1 = 40;
     }
    var myY2 = myY1+40;
    return [myY1, myX1, myY2, myX2];
}
function   solve_image(myTextFrame){
        if (myTextFrame.rectangles.length== 1){ 
              var x = myTextFrame.rectangles[0];
               x. geometricBounds = [myTextFrame.geometricBounds[0], 17, myTextFrame.geometricBounds[0]+40, 65];
               x.appliedObjectStyle = myDocument.objectStyles.itemByName("grafik"); 
        }
 }


 
Bevor wir das Skript starten, muss die zweite Seite wieder leer sein.

Bilder



Die Funktion main() ermittelt die Anzahl der direkten Kindelemente von "root"

Es wird eine Schleife gestartet, die entsprechend der Anzahl die Funktion lap() aufruft.

var inhalt= myDocument.xmlElements.item(0).xmlElements.item(u);

Dort wird zuerst der jeweilige Wert des XML-Elementes an die Variable inhalt übergeben.

u=u+1
Bei jedem Durchgang wird der nächste Wert an die Variable inhalt übergeben:

Hinweis: Man hätte es natürlich einfacher / verkürzt schreiben können, aber wir werden später noch einmal die Funktion lap() erweitern.

Die zweite große Änderung sehen wir bei myGetBounds.

Wie gehabt die Auswertung, ob linke oder rechte Seite sowie die Werte für die horizontale Platzierung.

 if (mypage.textFrames.length > 1){
Bei der vertikalen Platzierung überprüfen wir, ob bereits ein Textfeld auf unserer aktuellen Seite besteht.

Falls ja, wird der obere Rand wie folgt berechnet.

[mypage.textFrames.length -2].geometricBounds[2]  +  15
Untere Zeile des vorherigen Textfeldes + 15 mm Abstand

Kurze Erklärung zu mypage.textFrames.length -2

Nehmen wir an, das zu platzierende Textfeld wäre das zweite Textfeld auf der Seite.

Dann hätten wir Textfeld [0] und Textfeld [1]. Der Wert für textFrames.length wäre aber dann 2. Deshalb wird das vorherige/erste Textfeld entsprechend berechnet.

Textfeld[mypage.textFrames.length -2].


Jetzt müssen wir nur noch dafür sorgen, dass ab einem bestimmten Wert eine neue Seite eingefügt wird und nichts über die Seiten hinausläuft.

Wir fügen eine if-Abfrage ein, welche überprüft, ob sich die untere Linie des Textfeldes unterhalb von 280 mm befindet, und starten gegebenfalls eine Funktion, die eine neue Seite einfügt und das Textfeld verschiebt.   

Löscht nun wieder alle Textfelder auf der Seite. Vorsicht: Beim Start des Skriptes ist ein Textfeld unterhalb der zweiten Seite gerutscht. Dieses bitte ebenfalls löschen.

Wir erweitern unsere Skriptdatei erneut.

var myDocument = app.documents.item(0);
var sitenumber = 1;
var u = 0
 
main();
function main() {
    var element_anzah = myDocument.xmlElements.itemByName("root").xmlElements.length;
    for ( var zaehl = 0; zaehl  < element_anzah; zaehl++) {
        lap()
        }
} 
 
 function lap () {
 var inhalt= myDocument.xmlElements.item(0).xmlElements.item(u);    
 set_textfeld(inhalt);
  u=u+1;
  }
   
  function set_textfeld(inhalt){
    var myPage = myDocument.pages.item(sitenumber);
    var myTextFrame = myPage.textFrames.add();
    myTextFrame.appliedObjectStyle = myDocument.objectStyles.itemByName("txt_feld");
    myTextFrame.sendToBack();

    inhalt.placeXML(myTextFrame);
     
    myTextFrame.geometricBounds = myGetBounds();
   
  if(myTextFrame.geometricBounds[2]>280) {
   setnewsite(myTextFrame);
}

    solve_image(myTextFrame);

    return
   }
 
function myGetBounds( ){
    mypage = myDocument.pages.item(sitenumber);  
    if(mypage.side == PageSideOptions.rightHand ){
          var myX1 = 227; var myX2 = 350;
    } else {
        var myX1 = 70; var myX2 = 193; 
            }        
    if (mypage.textFrames.length > 1){
        var myY1 = mypage.textFrames[mypage.textFrames.length -2].geometricBounds[2]+15;
      } else{
        var myY1 = 40;
     }
    var myY2 = myY1+40;
    return [myY1, myX1, myY2, myX2];
}

function setnewsite(myTextFrame){
 mypage = myDocument.pages.add();
        myTextFrame.move(mypage);
        sitenumber=sitenumber+1;
          myTextFrame.geometricBounds = myGetBounds( mypage);
   }


function   solve_image(myTextFrame){
        if (myTextFrame.rectangles.length== 1){ 
              var x = myTextFrame.rectangles[0];
               x. geometricBounds = [myTextFrame.geometricBounds[0], 17, myTextFrame.geometricBounds[0]+40, 65];
               x.appliedObjectStyle = myDocument.objectStyles.itemByName("grafik"); 
        }
 }


Bilder



 
Nachdem wir ein Textfeld platziert haben, wird überprüft, ob sich die unterste Linie des Textfeldes unterhalb des Y-Werts : 280 befindet.

if(myTextFrame.geometricBounds[2]>280)

Falls ja, wird die Funktion setnewsite() gestartet. Dort wird Folgendes gemacht.

mypage = myDocument.pages.add();
Eine neue Seite wird eingefügt.

myTextFrame.move(mypage);
Das Textfeld wird auf die neue Seite geschoben.

sitenumber=sitenumber+1;
Die Variable, die über die aktuelle Seite Auskunft gibt, wird hochgezählt.

myTextFrame.geometricBounds = myGetBounds( mypage);
Das Textfeld wird auf der neuen Seite erneut positioniert.

Wir haben bisher einfach unser Skript um neue Funktionen erweitert und haben ein ganz passables Ergebnis erhalten.

Zum Schluss werden wir das Skript noch einmal umschreiben, sodass die unterste Tabelle in ein eigenes Textfeld platziert wird.

Löscht noch einmal alle Textfelder sowie die dritte Seite.

Schauen wir uns noch einmal die Struktur der XML-Datei nach dem Import an.

Dem Element <textfeld> sind zwei weitere Elemente untergeordnet <main> und <hinweis>
Das Element <hinweis> beinhaltet nur die Hinweis-Tabelle.

Obwohl ich in der Beispiel-Datei bei allen Elementen eine Hinweis-Tabelle eingefügt habe, muss dieses nicht existieren. Das Element <main>, das alle wichtigen Daten über unser Produkt beinhaltet, muss es geben, es muss auch immer an 1. Position sein.

Wir speichern nun unser final.jsx als final_xpath.jsx ab.

Und ändern es zum letzten Mal.

final_xpath.jsx

var myDocument = app.documents.item(0);
var sitenumber = 1;
var u = 0
 
main();
function main() {
    var element_anzah = myDocument.xmlElements.itemByName("root").xmlElements.length;
    for ( var zaehl = 0; zaehl  < element_anzah; zaehl++) {
        lap()
        }
} 
 
function lap () {
  var myElement = "main";
  var root = myDocument.xmlElements[0].xmlElements[u];
  var inhalt= root.xmlElements.item(0);
 // entspricht myDocument.xmlElements.item(0).xmlElements.item(u).xmlElements.item(0);
 set_textfeld(inhalt,myElement);

  var myHinweis =  root.evaluateXPathExpression("hinweis");
  if (myHinweis.length >0 ){
            inhalt = myHinweis[0];
            myElement  = "hinweis";
           set_textfeld(inhalt,myElement);
  u=u+1;
  }
}
   
  function set_textfeld(inhalt,myElement){
    var mypage = myDocument.pages.item(sitenumber);
    var myTextFrame = mypage.textFrames.add();
    myTextFrame.appliedObjectStyle = myDocument.objectStyles.itemByName("txt_feld");
    myTextFrame.sendToBack();
    inhalt.placeXML(myTextFrame);
    switch(myElement){ 
        case "main":   myTextFrame.geometricBounds = myGetBounds();
     if(myTextFrame.geometricBounds[2]>280) {
   setnewsite(myTextFrame);
    }
        break;
        case "hinweis":  myTextFrame.geometricBounds = myGetBounds_hinweis();

        break;
        }
        solve_image(myTextFrame);   
        return 
    }
 
function myGetBounds_hinweis( ){
    mypage = myDocument.pages.item(sitenumber);  
    if(mypage.side == PageSideOptions.rightHand ){
          var myX1 = 227; var myX2 = 403;
    } else {
        var myX1 = 17; var myX2 = 193; 
            }        
       
    var myY1 = mypage.textFrames[mypage.textFrames.length -2].geometricBounds[2]+5;
    var myY2 = myY1+20;
    return [myY1, myX1, myY2, myX2];
} 
 
 
function myGetBounds( ){
    mypage = myDocument.pages.item(sitenumber);  
    if(mypage.side == PageSideOptions.rightHand ){
          var myX1 = 227; var myX2 = 350;
    } else {
        var myX1 = 70; var myX2 = 193; 
            }        
    if (mypage.textFrames.length > 1){
        var myY1 = mypage.textFrames[mypage.textFrames.length -2].geometricBounds[2]+15;
      } else{
        var myY1 = 40;
     }
    var myY2 = myY1+40;
    return [myY1, myX1, myY2, myX2];
}

function setnewsite(myTextFrame){
 mypage = myDocument.pages.add();
        myTextFrame.move(mypage);
        sitenumber=sitenumber+1;
          myTextFrame.geometricBounds = myGetBounds( mypage);
   }


function   solve_image(myTextFrame){
        if (myTextFrame.rectangles.length== 1){ 
              var x = myTextFrame.rectangles[0];
               x. geometricBounds = [myTextFrame.geometricBounds[0], 17, myTextFrame.geometricBounds[0]+40, 65];
               x.appliedObjectStyle = myDocument.objectStyles.itemByName("grafik"); 
        }
 }

 
Das Ergebnis, wenn wir das Skript starten.

Bilder

Screenshot ist aus CS5; bei euch werden die "Hinweistextfelder" größer sein, da auch hier das Objektformat txt_feld mit einer Mindesthöhe von 40 mm zugewiesen wird. Ihr könnt natürlich ein weiteres Objektformat anlegen und zuweisen.

Schauen wir uns das Skript genauer an.

Anmerkung: Ich habe hier switch case verwendet, um es leserlicher zu machen. Zudem könnten natürlich noch weitere Elemente ergänzt werden.

Als Erstes fällt auf, dass die Funktion lap() erweitert wurde.

var myElement = "main";

Eine neue Variable wurde eingefügt, die einen String-Wert beinhaltet.

var inhalt= root.xmlElements.item(0);
//  entspricht myDocument.xmlElements.item(0).xmlElements.item(u).xmlElements.item(0);

Die Variable inhalt beinhaltet nicht mehr den kompletten Wert des Elementes <produkt>, sondern nur noch den von <main>

Unsere bekannte Funktion set_textfeld wird gestartet und läuft ab, wie wir es bereits kennen.

Nur bevor das Textfeld platziert wird, überprüfen wir mit switch case, welchen Wert die mitgegebene Variable myElement hat.

Das erste Textfeld mit unserem Element <main> wird wie bisher platziert.

Die Funktion lap() läuft weiter.

var myHinweis =  root.evaluateXPathExpression("hinweis");
Um auszulesen, ob ein Knoten-Element-Hinweis besteht, nutzen wir evaluateXPathexpression

if (myHinweis.length >0 ){
Überprüfung, ob <hinweis> existiert.

Falls ja,
inhalt = myHinweis[0];
inhalt bekommt Wert von <hinweis>

 myElement  = "hinweis";
Wert der Variable myElement wird geändert.

Funktion set_textfeld wird gestartet.

Switch / cass erkennt, dass es sich um "hinweis" handelt, und startet eine neue Funktion zur Positionierung unseres Hinweis-Textfeldes.

myGetBounds_hinweis( )


Weitere Überlegungen

Wie ihr seht, muss ein wenig Nacharbeit geleistet werden. Der Inhalt der Tabellenköpfe wurde aus den Tags der XML-Datei ausgelesen. Hier sind keine Sonderzeichen oder Umlaute erlaubt. Wir müssten also mit Suchen und Ersetzen zum Beispiel das Wort Größe innerhalb der Tabellenköpfe ändern (Groesse -> Größe).

Es findet auch keine Überprüfung statt, ob das Textfeld der Hinweis-Tabelle unter die Seite rutscht. Hierzu müsstet ihr eine neue Funktion erstellen, welche zum Beispiel folgende Aufgaben übernimmt:

Falls Hinweis-Textfeld unter 280
Die Textfelder <main> + <hinweis> gruppieren
Neue Seite anlegen
Gruppe auf neue Seite verschieben
Gruppe auflösen
Neu positionieren


 

Bilder

Um horizontale und vertikale Bilder unterschiedlich zu bearbeiten, müsstet ihr den Bildern bei der XML-Erstellung ein Attribut mitgeben, das ihr zum Beispiel "alignment" nennt und das den Wert "horizontal" oder "vertikal" einnehmen kann.

Den Wert könnt ihr dann per Skript auslesen und entsprechend das Bild platzieren.

.....  xmlElements[0].xmlAttributes.itemByName("alignment").value;


Tabellen?

Bei komplexen Tabellen (verbundene Zellen / Spalten) sollte die Umformung der Daten zur Tabelle schon vor der XSL-Transformation stattfinden, quasi bei der XML-Erstellung. Leider fehlen mir hier Erfahrungswerte.

Der umgekehrte Wert ist hier eventuell hilfreich. Tabelle in InDesign erstellen und diese als xml exportieren, um zu sehen, wie die XML-Daten auszusehen haben.


Musterseiten

Es dürfte an sich kein Problem sein, für verschiedene Kapitel unterschiedliche Musterseiten zu verwenden. Entweder per Skript oder ihr nutzt verschiedene Buchkapitel in InDesign.

Die Positionierung von Textfeldern nebeneinander ist natürlich auch möglich.


Hilfreiche Links

http://de.selfhtml.org/xml/darstellung/xsltelemente.htm
http://www.w3schools.com/xsl/
http://jongware.mit.edu/idcs6js/

Vielen Dank für eure Aufmerksamkeit.

Kritik / Verbesserungen / Ideen gerne an mich weiterleiten.




DVD-Werbung
Kommentare
Achtung: Du kannst den Inhalt erst nach dem Login kommentieren.
Portrait von Indesbeginner
  • 30.07.2017 - 15:14

Jetzt ist alles komplett :-) !
Danke!

Portrait von Domingo
  • 21.09.2014 - 22:42

Eine super Erklärung, vielen Dank!

Portrait von Caesarion2004
  • 21.09.2014 - 19:18

Vielen Dank für den weiteren, sehr interessanten Teil der Reihe!

Portrait von BOPsWelt
  • 21.09.2014 - 15:48

Vielen Dank für einen weiteren Teil, klasse. :-)

Portrait von pallasathena
  • 21.09.2014 - 15:25

Vielen Dank! War wieder eine eingehende Beschreibung!

x
×
×