InDesign Library - Asset-Namen auf Existenz prüfen

Hier werden Fragen und Probleme zur Anwendungsentwicklung mit InDesign diskutiert.
Antworten
wernerperplies
Beiträge: 250
Registriert: 6. Aug 2011, 17:48
Wohnort: 18374 Zingst
Kontaktdaten:

InDesign Library - Asset-Namen auf Existenz prüfen

Beitrag von wernerperplies » 3. Okt 2011, 17:16

In der HilfDirSelbst-Skriptwerkstatt
wurde die Frage aufgeworfen, wie man möglichst schnell feststellen kann, ob ein Asset mit einem bestimmten Namen in einer Library bereits existiert, und wenn ja, soll ein neuer, eindeutiger Name mit angehängter Nummer erzeugt werden.

Die bisher benutzte Lösung sah so aus:

Code: Alles auswählen

counter=0; 
var Templatename_full = Templatename;  
do 
{ 
   counter+=1 
   Templatename_full = Templatename+" "+counter 
   var Templatename_vorhanden =   lib_prod.assets.itemByName(Templatename_full).isValid 
} 
while (Templatename_vorhanden == true); 
Obwohl diese Schleife durchaus noch etwas zu optimieren wäre, liegt die eigentliche Ursache für das Geschwindigkeitsproblem in dieser Zeile:

Code: Alles auswählen

lib_prod.assets.itemByName(Templatename_full).isValid
Es sieht so aus, dass dieser Befehl immer mit einem Dateizugriff verbunden ist, und jeder Aufruf wieder mit der Suche am Dateianfang beginnt.

Um die Problematik des Fragestellers nachstellen zu können erstelle ich mir im ersten Schritt eine Library mit 260 Einträgen, deren Namen sich aus einem Buchstaben und einer vierstelligen laufenden Nummer ergibt, z. B:
a0000,a0001... z0009

Der zugehörige Programmcode sieht so aus:

Code: Alles auswählen

String.prototype.fill = function(number){var filled ="";for (var i=0;i<number;++i){filled += this;}return filled;}
String.prototype.padLeft = function(fillc, length)
{if (this.length<length){return (fillc.fill(length-this.length)+this).substr(-length);}return this;}
main();

function main()
{
app.doScript(createLib,ScriptLanguage.JAVASCRIPT,undefined, UndoModes.AUTO_UNDO);
}
function createLib()
{
   var lib = app.libraries.add("C:/Dokumente und Einstellungen/Administrator/Eigene Dateien/Indesign SkriptTests/library/test.indl");
   var chars = "abcdefghijklmnopqrstuvwxyz";
   var startTime = new Date().getTime();
    for (i=0;i<chars.length;i++)
    {
        for (j=0;j<10;j++)
        {
            var asset = lib.store(app.activeDocument.pageItems[0]);
            asset.name = chars[i]+(j.toString()).padLeft("0",4); 
        }
    }
    lib.close();
   var neededTime = getUsedTime(startTime);
   alert(neededTime);    
}
function getUsedTime(startTime)
{
    var neededTime = new Date().getTime()-startTime;
    neededTime = new Date().setTime(neededTime);
    var seconds = neededTime/1000;
    var minutes = 0;
    var hours = 0;
    if (seconds>60)
    {
        minutes = Math.floor(seconds/60);
        seconds = seconds-minutes*60;
        if (minutes>60)
        {
            hours = Math.floor(minutes/24);
            minutes = minutes-hours*60
        }
    }
    return [hours, minutes, seconds];
}
Um die Anzahl der Festplattenzugriffe zu minimieren, lese ich am Anfang alle Assetnamen in ein Array ein, und sortiere dieses Array nach den vollständigen Namen:

Code: Alles auswählen

String.prototype.fill = function(number){var filled ="";for (var i=0;i<number;++i){filled += this;}return filled;}
String.prototype.padLeft = function(fillc, length)
{if (this.length<length){return (fillc.fill(length-this.length)+this).substr(-length);}return this;}
main();

function main()
{
    // app.doScript(createLib,ScriptLanguage.JAVASCRIPT,undefined, UndoModes.AUTO_UNDO);
    app.doScript(store2Array,ScriptLanguage.JAVASCRIPT,undefined, UndoModes.AUTO_UNDO);
}
function store2Array()
{
    var lib = app.open("C:/Dokumente und Einstellungen/Administrator/Eigene Dateien/Indesign SkriptTests/library/test.indl");
    var startTime = new Date().getTime();
    var allAssets = new Array();
    for (i=0;i<lib.assets.length;i++)
    {
        allAssets.push({commonName: lib.assets[i].name.substr(0,1), fullName: lib.assets[i].name});
    }
    allAssets = allAssets.sort(sortElements);
Anschließend erzeuge ich ein Array mit Elementen für jeden Buchstaben und einem Array mit den vollständigen Namen der einzelnen Elemente:

Code: Alles auswählen

    if (allAssets.length>1)
    {
        var assetNames = new Array();
        var assetOneName = {commonName: allAssets[0].commonName, subAssets: [{name: allAssets[0].fullName}]};        
        for (i=1;i<allAssets.length;i++)
        {
            var subAsset = {name: allAssets[i].fullName};
            if (allAssets[i].commonName == allAssets[i-1].commonName) 
                assetOneName.subAssets.push(subAsset);
            else 
            {
                assetNames.push(assetOneName);
                assetOneName = {commonName: allAssets[i].commonName, subAssets: [subAsset]};
            }
        }
        if (assetNames.length > 0)
        {
            if (assetNames[assetNames.length-1].commonName != assetOneName.commonName)
            {
                assetNames.push(assetOneName);
            }    
        }
    }    
    var neededTime = getUsedTime(startTime);
    alert(neededTime);    

Das Erstellen der Arrays ist bei größeren Libraries ein einmaliger Zeitaufwand, der bei einer weiteren Optimierungsstufe komplett in eine eigene Datei ausgelagert werden könnte, um nicht bei jeden Skriptstart erneut ausgeführt werden zu müssen.

Die Routine getNewNameFromArray(assetNames, "z") führt jetzt die Suche nach einem Eintrag für den Buchstaben "z" aus, ermittelt die Anzahl der "z"-Einträge und erzeugt einen neuen Dateinamen:

Code: Alles auswählen

    var startTime = new Date().getTime();
    var newName = getNewNameFromArray(assetNames, "z");
    var neededTime = getUsedTime(startTime);
    alert(neededTime);    


Die zugehörige Funktion:

Code: Alles auswählen

function getNewNameFromArray(groupedAssets, name)
{
        for (var i=0; i<groupedAssets.length;i++)
        {
            if (groupedAssets[i].commonName==name)
            {
                // found!
                // construct new name
                var newName = name+(groupedAssets[i].subAssets.length+1).toString().padLeft("0",4);
                groupedAssets.push({name: newName});
                return newName;
            }
        }
        var newName = name+("1").padLeft("0",4);
        groupedAssets.push({commonName: name, subAssets: [{name: newName}]});
        return newName;       
}
Zum Vergleich noch ein Aufruf der leicht modifizierten Routine des Fragestellers:
var startTime = new Date().getTime();
var newName = getNewNameFromLib(lib, "z");
var neededTime = getUsedTime(startTime);
alert(neededTime);


Die zugehörige Funktion:

Code: Alles auswählen

function getNewNameFromLib(lib, name)
{
    var counter=0; var newName = "";
    do 
    { 
        ++counter;
        newName = name+((++counter).toString()).padLeft("0",4); 
     } 
    while (lib.assets.itemByName(newName).isValid); 
    return newName;
}

Das vollständige Skript gibt's hier:
libTest.zip
Zum Schluss eine Gegenüberstellung der Zeiten,
260 Einträge:
Bild
Bild
2600 Einträge:
Bild
Bild
einen schönen Tag wünscht

Werner Perplies
https://www.weepee.de

wernerperplies
Beiträge: 250
Registriert: 6. Aug 2011, 17:48
Wohnort: 18374 Zingst
Kontaktdaten:

Re: InDesign Library - Asset-Namen auf Existenz prüfen

Beitrag von wernerperplies » 7. Okt 2011, 07:23

Ich habe hier
Bibliothek (library) - Prüfen, ob es ein bestimmtes Element schon gibt. (von: JuMayr)
eine Antwort auf meinen Beitrag erhalten.

Hallo Jürgen,

ja, Deine Lösung bringt natürlich eine erhebliche Geschwindigkeitssteigerung gegenüber Deiner ursprünglichen Lösung, ist aber langsamer als meine Lösung.

Dies dürfte aber bei der geringen Anzahl der Elemente, die vernünftigerweise in einer Library vorhanden sind (auch einige tausend sind wenige!) keine große Rolle spielen.

Deine Lösung ist von der Idee ja ähnlich aufgebaut, wie meine Lösung, hat aber aus meiner Sicht Nachteile.
Du sparst Dir zwar den Aufbau des Array mit SubArrays, gibst aber alle Vorteile des einmaligen Aufbaus auf.

Nachteile:
  1. Du erzeugst für jede Namensanforderung ein neues Array mit neuen Inhalten, das kostet Zeit.
  2. Da Du Dein Array nicht sortierst, musst Du die ganze Schleife mit Vergleichen durchlaufen, obwohl Du vielleicht schon alle Elemente gefunden hast, auch das kostet unnötig Zeit.
Sobald Du Deine Array wenigstens sortiert hast, kannst Du Deine Logik in zwei Punkten optimieren:
  1. die Suche nach dem letzten Treffer abbrechen.
  2. binär Suchen:
    damit beginnst Du Deine Suche nicht am Anfang des Arrays, sondern in der Mitte und vergleichst, ob Dein Name in der unteren Hälfte, oder in der oberen Hälfte vorhanden ist, in Abhängigkeit vom Ergebnis suchst Du dann im nächsten Teil usw mit einer rekursiven Funktion.
Aber, wie schon gesagt, entscheidend ist, dass Du mit Deinem Ergebnis zufrieden bist.
einen schönen Tag wünscht

Werner Perplies
https://www.weepee.de

Antworten

Zurück zu „Fragen zur Programmierung“