#import "IntarS.h"
#ifdef GNU_RUNTIME
#import <objc/objc-api.h>
#else
#import <objc/objc-class.h>
#endif
//	IntarS
//	copyright Pirmin Braun 1997-2006 - pirmin@pirmin.de
//	all Rights reserved;

#define HANDLE_SLOWDB \
        if(ts>20.0){\
            int maxSlowDBCount = [[[_APP configDict]ofk:@"maxslowdbcount"]intValue];\
            slowDBCount++;\
            LOGS(([NSSWF @"current slowDBCount: %i",slowDBCount]));\
            if(maxSlowDBCount && slowDBCount > maxSlowDBCount)exit(0);\
        }

// weitere Kategorien


@implementation Application (DB)
- (BOOL)isReferencedEO:(PBEO *)eo;
{
// ermittelt, ob eo von einem anderen referenziert wird
    if(!eo)return NO;
    if(![eo isKindOfClass:[PBEO class]])return NO;
    {
        PBDDTable *t = [eo myTable];
        NSArray *a = [t attributesReferencingMe];
        int i,j;
        for(i=0,j=[a count];i<j;i++){
            PBDDAttribute *pba = [a oai:i];
            NSString *sql;
            NSString *s;

            LOG(@"1");
            sql = [NSSWF @"select %@ from %@ where %@ = '%@' limit 1",[[pba myTable]primaryKeyName],[[pba myTable]dbName],[pba dbName],[eo primaryKey]];
            LOG(sql);
            s = singleValueSQL(sql);
            if(FILLED(s))return YES;
        }
    }
    return NO;
}
- (NSArray *)referencingEO:(PBEO *)eo;
{
// liefert ein Array von Dictionaries aller Tabellen mit primary Keys, die auf das eo verweisen; max. 100
// fuer Portlet "Objektverwendung"
    LMA;
    if(!eo)return lma;
    if(![eo isKindOfClass:[PBEO class]])return lma;
    {
        PBDDTable *t = [eo myTable];
        NSArray *a = [t attributesReferencingMe];
        int i,j;
        for(i=0,j=[a count];i<j;i++){
            PBDDAttribute *pba = [a oai:i];
            NSString *tableName = [[pba myTable]dbName];
            NSString *guiTableName = [[pba myTable]guiName];
            NSString *guiName = [pba guiName];
            NSString *dbName = [pba dbName];
            NSString *sql = [NSSWF @"select %@ from %@ where %@ = '%@' limit 100",[[pba myTable]primaryKeyName],tableName,[pba dbName],[eo primaryKey]];
            NSArray *a1 = [_APP getStringArrayAsResultFrom:sql];
            int i1,j1;

            for(i1=0,j1=[a1 count];i1<j1;i1++){
                NSString *pk = [a1 oai:i1];
                LMD;
                [lmd setObject:pk forKey:@"primaryKey"];
                [lmd setObject:tableName forKey:@"tableName"];
                [lmd setObject:guiTableName forKey:@"guiTableName"];
                [lmd setObject:guiName forKey:@"guiName"];
                [lmd setObject:dbName forKey:@"dbName"];
                [lma addObject:lmd];
                if([lma count]>= 100)return lma;
            }
        }
    }
    return lma;
}
- (NSString *)attributAuflistungForTable:(PBDDTable *)t withDoku:(BOOL)withDoku;
{
    NSString *s = EON;
    int ii,jj;
    NSArray *a1 = [t plainAttributes];

    s = [s stringByAppendingFormat:@"----Tabelle %@ hat diese Attribute:\n\n",[t dbName]];
        for(ii=0,jj=[a1 count];ii<jj;ii++){
            PBDDAttribute *pba = [a1 oai:ii];
            NSString *refdTableName,*line;

            if(![pba isVisible])continue;
            if([pba targetTyp] != ATTVCSELOBJ)continue;
            refdTableName = [pba refdTableName];
            line = [NSSWF @"%%selObj.%@                                                        ",[pba dbName]];
            line = [line substringToIndex:50];
            if(FILLED(refdTableName)){
                s = [s stringByAppendingFormat:@"%@  %@ --->%@\n",line,[pba guiName],refdTableName];
            }else{
                s = [s stringByAppendingFormat:@"%@  %@\n",line,[pba guiName]];
            }
            if([pba hasVL]){
                int i1,j1;

                s = [s stringByAppendingString:@"\tNV:::\n"];
                for(i1=0,j1=[[pba vl] count];i1<j1;i1++){
                    PBVLO *pbvlo = [[pba vl]oai:i1];
                    s = [s stringByAppendingFormat:@"\t%@\t%@\n",[pbvlo value],[pbvlo bez]];
                }
            }
            if(withDoku){
                NSString *doku = [pba doku0];
                if(FILLED(doku))s = [s stringByAppendingFormat:@"\t%@\n",doku];
            }
        }
        return s;
}
- (NSString *)descriForEn:(NSString *)en pk:(NSString *)pk;
{
    NSString *sql,*s=nil,*guiValue;
    PBDDAttribute *da;
    NSArray *a;
    PBDDTable *t;
    int i,j;
    NSDictionary *d;

    if(!en || !pk)return EON;
    t = [MYDD tableNamedCheap:en];
    if(!t)return EON;
    a = [t descriAttributes];
    if(![a count])return EON;

    sql = [NSSWF @"select %@ from %@ where %@ = '%@'",[[a valuesForKey:@"dbName"]componentsJoinedByString:@","],en,[t primaryKeyName],[pk mysqlEscapedString]];
    d = [self getDictAsResultFrom:sql];
    if(!d)return EON;
    for(i=0,j=[a count];i<j;i++){
        da = [a oai:i];
        guiValue = [d ofk:[da dbName]];
        if(!s){
            s = guiValue;
        }else{
            if([da hasVL]){
                guiValue = [da bezeichnungForValue:guiValue];
            }
            s = [s stringByAppendingFormat:@"-%@",guiValue];
        }
    }
    s = EONNIL(s);
    return [s abbreviated40String];
}
///////////////////////////////////////////////////////////////////////////////////////////
//	sequentielle Verarbeitungen
///////////////////////////////////////////////////////////////////////////////////////////
- (void)addFetchRequest:(NSDictionary *)fetchReq forHandle:(NSString *)handle;
{
    if(!handle || !fetchReq)return;
    if([seqAccesses ofk:handle]){
        LOGS(([NSSWF @"handle %@ bereits in Gebrauch;",handle]));
        return;
    }
    [seqAccesses setSecureObject:fetchReq forKey:handle];
}
- (NSDictionary *)fetchReqForHandle:(NSString *)handle;
{
    return [seqAccesses ofk:handle];
}
- (BOOL)setFetchCond:(EOQualifier *)q forEN:(NSString *)entityName soa:(NSArray *)soa handle:(NSString *)handle;
{
    return [self setFetchCond:(EOQualifier *)q forTable:(PBDDTable *)[MYDD tableNamed:entityName] soa:(NSArray *)soa handle:(NSString *)handle];
}
- (void)endFetchingForHandle:(NSString *)handle;
{
    NSDictionary *fetchReq = [self fetchReqForHandle:handle];
    PBMySQLChannel *ac;

    if(!fetchReq)return;
    ac = [fetchReq objectForKey:SC_channel];
    if([ac isFetchInProgress])[ac cancelFetch];

    [seqAccesses removeObjectForKey:handle];
}

- (BOOL)establishConnection;
{
    [self closeAllChannels];
    if(([self freshChannel]!=nil)){
        return YES;
    }else{
        LOGS(@"freshChannel nicht bekommen");
    }
    return NO;
}
- (BOOL)setFetchCond:(EOQualifier *)q forTable:(PBDDTable *)myTable soa:(NSArray *)soa handle:(NSString *)handle;
{
    PBMySQLChannel *ac;
    NSDictionary *fetchReq;
    NSString *wc;
    NSString *s;
    NSString *ttn;

    double ts=0.0;


    if(!handle){
        LOGS((@"handle nicht angegeben"));
        return NO;
    }

    if([_APP collectIndexStatistics]){
        ts = [NSDate timeIntervalSinceReferenceDate];
    }
    [self endFetchingForHandle:handle]; //zur sicherheit evt. alte handle beenden

    wc = [self whereClauseFrom:q forTable:myTable];
    if(FILLED(wc))wc = [NSSWF @" where %@",wc];
    //temp table aufbauen, gesamten Satz reinstellen;
    //damit muss nicht nochmal mit pk auf echte Table zugegriffen werden

    ttn = [[NSSWF @"tmp_%@",handle]lowercaseString]; //lower, da tablenamen generell lowercase gemacht werden
    [self evaluateSQL:[NSSWF @"drop temporary table if exists %@",ttn]];
    s = [NSSWF @"create temporary table %@ select * from %@ %@",ttn,[myTable dbName],wc];
    if(![self evaluateSQL:s])return NO;
    if(!(ac = [self freshChannel]))return NO;
    if(![ac selectAttributes:nil qualifier:nil forTable:myTable tn:ttn offset:0 count:0 soa:soa])return NO;
    fetchReq = [NSDictionary dwok:ac,SC_channel,myTable,SC_pbt,@"YES",@"forUpdate",nil,nil];

    [self addFetchRequest:fetchReq forHandle:handle];
    if([_APP collectIndexStatistics]){
        ts = [NSDate timeIntervalSinceReferenceDate] - ts;
        if(ts>1.0)LOGS(([NSSWF @"%03f sec. ### setFetchCond: table %@ q %@ soa %@",ts,[myTable dbName],[q description],[soa description]]));
        HANDLE_SLOWDB;
    }
    return YES;
}

- (PBEO *)nextEOForHandle:(NSString *)handle;
{
    //liefert autoreleastes EO;
    // daher immer schoen mit AutoreleasePools arbeiten;
    // nach dem letzten wird fetch autom. endFetchingForHandle: gemacht
    //letzte erfolgreich verwendete channel wird gemerkt f. evt. update
    NSDictionary *fetchReq = [self fetchReqForHandle:handle];
    PBDDTable *myTable = (PBDDTable *)[fetchReq objectForKey:SC_pbt];
    NSString *entityName = [myTable dbName],*pk;
    NSMutableDictionary *md;
    PBEO *eo=nil;
    PBMySQLChannel *ac;

    if(!fetchReq || !myTable){
        LOG(@"no fetchReq or table");
        return nil;
    }
    ac = [fetchReq objectForKey:SC_channel];
    if(![ac isFetchInProgress]){
//        LOG(@"ac no fetch in progress");
        [self endFetchingForHandle:handle];
        return nil;
    }
    if(!(md=[ac fetchRow])){ //den naechsten key aus temp-table
//        LOG(@"no result for fetchRow");
        [self endFetchingForHandle:handle];
    }else{
        //md stammt evt. aus temptable, ist aber vollstaendig;
        pk = [md ofk:[myTable primaryKeyName]];
        eo = [self eoFromDict:md entityName:entityName];
        [eo setWasFetched:YES];
    }
    return eo;
}
- (NSMutableDictionary *)nextMDForHandle:(NSString *)handle;
{
    //liefert autoreleastes md;
    NSDictionary *fetchReq = [self fetchReqForHandle:handle];
    NSMutableDictionary *md;
    PBMySQLChannel *ac;

    if(!fetchReq)return nil;
    ac = [fetchReq objectForKey:SC_channel];
    if(![ac isFetchInProgress]){
        [self endFetchingForHandle:handle];
        return nil;
    }
    if(!(md=[ac fetchRow])){ //den naechsten key aus temp-table
        [self endFetchingForHandle:handle];
    }
    return md;
}

///////////////////////////////////////////////////////////////////////////////////////////
// 		EO-Umformungen
///////////////////////////////////////////////////////////////////////////////////////////
- (PBEO *)eoFromDict:(NSDictionary *)d entityName:(NSString *)entityName;
{
    PBEO *eo;
    
    eo = [[[PBEO alloc]initWithEntityName:entityName]autorelease];
    if(!eo)return nil;
    [eo useValuesFromDictionary:d];
    return eo;
}
- (NSMutableDictionary *)dictFromString:(NSString *)s table:(PBDDTable *)t;
{
// f. fixedPosition Schnittstellen: ein String wird gemaess den Tableattributen in ein Dictionary verwandelt
    NSArray *a = [t plainAttributes];
    NSString *s1;
    NSRange r;
    PBDDAttribute *pbat;
    int i,j,o=0,sl;
    LMD;

    if(!FILLED(s))return lmd;
    if(!t)return lmd;
    sl = [s length];
    for(i=0,j=[a count];i<j;i++){
        pbat = [a oai:i];
        r.location=o;
        r.length=[pbat length];
        if((o + r.length) <= sl){
            s1 = [s substringWithRange:r];
            o+=r.length;
        }else{
            s1 = EON;
        }
        [lmd setSecureObject:s1 forKey:[pbat dbName]];
    }
//    LOGS([lmd description]);

    return lmd;
}

- (NSDictionary *)dictFromEO:(PBEO *)eo forKeys:(NSDictionary *)keyDict;
{
    PBDDTable *myTable = [myDD tableNamed:[eo entityName]];
    NSArray *a = ATisDB(myTable);
    PBDDAttribute *pba;
    NSString *k;
    NSObject *s;
    int i,j=[a count],dt;
    LMDS(md,j);

//    LOGS([keyDict description]);
//    LOGS([[eo values] description]);
    for(i=0;i<j;i++){
        pba = [a oai:i];
        k = [pba dbName];
        if(keyDict && ![keyDict ofk:k])continue;
        s = [eo vfk:k];
        if(!FILLED(s)){
            [md setSecureObject:EON forKey:k];
            continue;
        }
//die richtigen Typen f. Adaptor setzen
//bis auf Date genuegt evt. der String
        dt = [pba dataTyp];
        switch(dt){
            case DT_CHAR:
            case DT_MONEY:
            case DT_FLOAT:
            case DT_INT:
            case DT_BOOL:
                [md setSecureObject:s forKey:k];
                break;
            case DT_DATE:
                [GPBD setDBString:(NSString *)s];
                [md setSecureObject:[GPBD dateAsMysqlDBString] forKey:k];
                break;
            case DT_DATETIME:
                [GPBD setDBString:(NSString *)s];
                [md setObject:[GPBD dateAsMysqlDBDTString] forKey:k];
                break;
        }
    }
    return md;
}
///////////////////////////////////////////////////////////////////////////////////////////
// 		fetch methoden
///////////////////////////////////////////////////////////////////////////////////////////
- (BOOL)refetchEO:(PBEO *)eo;
{
    NSString *entityName = [eo entityName];
    PBDDTable *myTable = [MYDD tableNamed:entityName];
    NSMutableDictionary *md;
    NSArray *aa;
    PBMySQLChannel *ac;
    EOQualifier *q;

    if(!(eo))return NO;
    if(!(myTable))return NO;
    q = [eo pkq];
    if([q isKindOfClass:[EONothingQualifier class]])return NO;
    //restl. Werte beschaffen
    if(!(ac = [self freshChannel]))return NO;
    aa = ATisDB(myTable);
    if(![ac selectAttributes:aa qualifier:q forTable:myTable offset:0 count:1 soa:nil])return NO;
    if((md=[ac fetchRow])){
        PBEO *eo1=[self eoFromDict:md entityName:entityName];

        [[eo values]removeAllObjects];
        [[eo values]addEntriesFromDictionary:[eo1 values]];
        [eo setWasFetched:YES];
    }else{
        return NO;
    }
    if([ac isFetchInProgress])[ac cancelFetch];
    return YES;
}
- (NSArray *)getBasicEOs:(EOQualifier *)q entityName:(NSString *)entityName offset:(int)offset count:(int)count  soa:(NSArray *)soa;
{
// offset und count werden als limit beim select uebergeben

    PBDDTable *myTable;
    NSMutableDictionary *md;
    NSArray *aa;
    PBEO *fetchedEO;
    LMAS(ma,50);
    PBMySQLChannel *ac;

    if(!(myTable = [MYDD tableNamed:entityName]))return nil;

    if(!(ac = [self freshChannel]))return nil;
    aa = ATisDB(myTable);
    if(![ac selectAttributes:aa qualifier:q forTable:myTable offset:offset count:count soa:soa]){
        LOGS(@"select Attributes failed");
        return nil;
    }
    while((md=[ac fetchRow])){
        fetchedEO = [self eoFromDict:md entityName:entityName];

        [ma addObject:fetchedEO];
        [fetchedEO setWasFetched:YES];
    }
    if([ac isFetchInProgress])[ac cancelFetch];
    return ma;
}
- (PBEO *)eoOfRelation:(NSString *)s forEo:(PBEO *)eo;
{
   NSString *fk = [eo vfk:s];
   PBDDAttribute *pba;

   if(!eo)return nil;
   if(![eo isKindOfClass:[PBEO class]])return nil;

   pba = [[myDD tableNamed:[eo entityName]] plainAttrNamed:s];

   if(!pba){
       LOGS(([NSSWF @"keine Relation %@ in %@ fuer %@",s,[eo entityName],[eo description]]));
       // PRINTCURRENTSTACK;
       return nil;
   }
   if(!FILLED(fk))return nil;
   return getEOPkValue([pba refdTableName],fk);
}
- (NSArray *)getEOs:(NSString *)entityName qualifier:(EOQualifier *)q offset:(int)offset count:(int)count soa:(NSArray *)soa;
{
   double ts=0.0;
   NSArray *a;

   if([q isKindOfClass:[EONothingQualifier class]])return nil;
   if([_APP collectIndexStatistics]){
       ts = [NSDate timeIntervalSinceReferenceDate];
   }
   a = [self getBasicEOs:q entityName:entityName offset:offset count:count soa:soa];

   if([_APP collectIndexStatistics]){
       ts = [NSDate timeIntervalSinceReferenceDate] - ts;
       if(ts>1.0)LOGS(([NSSWF @"%03f sec. ### getEOs: table %@ q %@  soa %@",ts,entityName,[q description],[soa description]]));
       HANDLE_SLOWDB;
   }
   return a;
}
/* code-beispiel
v = [NSSWF @"select %@ from %@ where %@ = '%@'",[kps oai:1],rtn,[[MYDD tableNamedCheap:rtn] primaryKeyName],v];
v = [_APP getSingleValueAsResultFrom:v];
*/
///////////////////////////////////////////////////////////////////////////////////////////
// 		key vergabe
///////////////////////////////////////////////////////////////////////////////////////////

- (PBEO *)newAutoNumberEntryForTableNamed:(NSString *)entityName withLastKey:(BOOL)withLastKey;
{
    PBEO *aneo; //autonumber
    NSString *s=@"0";

    if(withLastKey){
        s = [self lastKeyForTableNamed:entityName];
        if(!s)return nil;
    }

    aneo = [self createEOforEN:@"autonumber"];
    [aneo tvfk(entityName,@"tablename")];
    [aneo tvfk(s,@"actualnumber")];
    if(![self insertEO:aneo])return nil;
    return aneo;
}
- (NSString *)lastKeyForTableNamed:(NSString *)entityName;
{
// table ist bereits gelocked
// macht nur sinn bei durchgaengig hochgezaehlten keys ohne Prefix; 

    int	i=0;
    PBDDTable *myTable = [MYDD tableNamed:entityName];

    if(!myTable)return nil;
    i = [singleValueSQL(([NSSWF @"select max(%@) from %@;",[myTable primaryKeyName],entityName])) intValue];
    return [NSS(i) secure0LPaddedSubstringToIndex:[[myTable primaryKeyAttr]length]];
}
///////////////////////////////////////////////////////////////////////////////////////////
// 		low level sql
///////////////////////////////////////////////////////////////////////////////////////////
- (unsigned)evaluateSQL:(NSString *)s;
{
//wenn kein result benoetigt wird, diese Methode nehmen
//stellt sicher, dass ein eventuelles result gefreed wird und der channel damit wieder zur verfuegung steht
//wird ein result benoetigt, die gleichnamige Methode am Channel aufrufen und selber dafuer sorgen, dass der fetch beendet wird;
    PBMySQLChannel *ac;
    unsigned result;
    double ts=0.0;

    if(!FILLED(s))return 0;
    if(!(ac = [self freshChannel])){
        LOG(@"--- keinen Channel bekommen");
        return 0;
    }
    if([_APP collectIndexStatistics]){
        ts = [NSDate timeIntervalSinceReferenceDate];
    }

    result = [ac evaluateSQL:s];
    [ac cancelFetch];

    if([_APP collectIndexStatistics]){
        ts = [NSDate timeIntervalSinceReferenceDate] - ts;
        if(ts>1.0)LOGS(([NSSWF @"%03f sec. ### evaluateSQL: %@",ts,s]));
        HANDLE_SLOWDB;
    }


    return result;
}
- (NSString *)getSingleValueAsResultFrom:(NSString *)s;
{
// returned single Value
// falls was nicht klappt, EON

    PBMySQLChannel *ac;
    NSString *rs;
    unsigned result;
    NSDictionary *d;

    if(!FILLED(s))return EON;
    if(!(ac = [self freshChannel]))return EON;
// LOGS(s);
    result = [ac evaluateSQL:s];
    if(![ac isFetchInProgress])return EON;
    [ac setSelectedAttributes:[ac describeResults]];
    d = [ac fetchRow];
    [ac cancelFetch];
    if(!d)return EON;
// LOGS([d description]);
    rs = [[d allValues]firstObject];
    if(!FILLED(rs))rs=EON;
    return rs;
}
- (NSDictionary *)getDictAsResultFrom:(NSString *)s;
{
// returned single dictionary

    PBMySQLChannel *ac;
    unsigned result;
    NSDictionary *d;

    if(!FILLED(s))return nil;
    if(!(ac = [self freshChannel])){
        LOG(@"keinen Channel bekommen.");
        return nil;
    }
// LOGS(s);
    result = [ac evaluateSQL:s];
    if(![ac isFetchInProgress])return nil;
    [ac setSelectedAttributes:[ac describeResults]];
    d = [ac fetchRow];
    [ac cancelFetch];
    return d;
}
- (NSArray *)getArrayAsResultFrom:(NSString *)s;
{
// returned array of dictionaries

    PBMySQLChannel *ac;
    unsigned result;
    NSDictionary *d;
    LMA;

    if(!FILLED(s))return lma;
    if(!(ac = [self freshChannel]))return lma;
    result = [ac evaluateSQL:s];
    if(![ac isFetchInProgress])return lma;
    [ac setSelectedAttributes:[ac describeResults]];

    while((d=[ac fetchRow])){
        [lma addObject:d];
    }
    [ac cancelFetch];
    return lma;
}
- (NSArray *)getStringArrayAsResultFrom:(NSString *)s;
{
    NSArray *a3 = [_APP getArrayAsResultFrom:s]; //das sind dictionaries
    LMAN(lma3);
    int i3,j3;
    for(i3=0,j3=[a3 count];i3<j3;i3++){
        [lma3 addObject:[[[a3 oai:i3]allObjects]firstObject]];
    }
    return lma3;
}

///////////////////////////////////////////////////////////////////////////////////////////
// 		transactions
///////////////////////////////////////////////////////////////////////////////////////////
- (BOOL)lockTables:(NSString *)tablesList;
{
    NSArray *a = [tablesList componentsSeparatedByString:@","];
    int i,j;
    NSMutableString *ms;

    if(![a count])return [self unlockTables];
    ms = [NSMutableString stringWithCapacity:100];
    [ms setString:@"LOCK TABLES "];
    for(i=0,j=[a count];i<j;i++){
        if(i)[ms appendString:@", "];
        [ms appendFormat:@"%@ WRITE",[a oai:i]];
    }
    [ms appendString:@";"];
//    if([_APP orbDebug])LOGS(([NSSWF @"locked tables: %@",[a description]]));
    return ([self evaluateSQL:ms]>0);
}
- (BOOL)unlockTables;
{
//    if([_APP orbDebug])LOGS((@"locked tables: ()"));
    return ([self evaluateSQL:@"UNLOCK TABLES;"]>0);
}
///////////////////////////////////////////////////////////////////////////////////////////
// 		EO Handling
///////////////////////////////////////////////////////////////////////////////////////////
- (void)copyPosFrom:(PBEO *)k1 to:(PBEO *)k2 posEn:(NSString *)posEn;
{
// nach 
    PBEO *p2,*p1;
    NSArray *a;
    int i,j;

    if(!k1 || !k2)return;
    a = [self positionenFor:k1 posEn:posEn];
    for(i=0,j=[a count];i<j;i++){
        p1 = [a oai:i];
        p2 = NEW_EO(posEn);

        [p2 takeValuesOfSameNameFromEo:p1];
        [p2 tvfk([k2 primaryKey],@"masterkey")]; //Kopfkey 2 in neue pos haengen
        [p2 tvfk(nil,[p2 primaryKeyName])];
        [parmDict setObject:k1 forKey:@"p_k1"];
        [parmDict setObject:k2 forKey:@"p_k2"];
        [parmDict setObject:p1 forKey:@"p_p1"];
//will/didInsert/didCreate macht keinen Sinn, weil diese davon ausgehen, den PBWOPosEditor oder einen PBWOEditor als datasource zu haben
        [parmDict setObject:p2 forKey:@"p_eo"];
        [_APP executeScriptNamed:[NSSWF @"_Application/%@/willCopyPos",[p2 entityName]] datasource:(PBWOEditor *)self parmDict:parmDict];
        INSRT(p2);
        [_APP executeScriptNamed:[NSSWF @"_Application/%@/didCopyPos",[p2 entityName]] datasource:(PBWOEditor *)self parmDict:parmDict];
    }
}
- (NSArray *)positionenFor:(PBEO *)k1 posEn:(NSString *)posEn;
{
    return getEOsKnKv(posEn,@"masterkey",[k1 primaryKey]);
}
- (PBEO *)createEOforEN:(NSString *)entityName;
{
    PBEO	*eo;
    NSArray	*allAttr;
    int	i,j;
    int dt;
    PBDDTable	*myTable = [myDD tableNamed:entityName];
    PBDDAttribute *cdate = [myTable plainAttrNamed:@"cdate"];
   // Felder initialisieren

    if(!myTable){
        return nil;
    }
    eo = [[PBEO alloc]initWithEntityName:entityName];
    if(!eo)return nil;

// Statistikfelder, damit schon mal was drinsteht, wenn man nach "neu" auf das System-Tab geht
    [[eo values] tvfk(CURRENTUSER,@"cuser")];
    [[eo values] tvfk(CURRENTUSER,@"luser")];
    if(cdate){
        if([cdate dataTyp]==DT_DATETIME){
            [[eo values] tvfk([_APP now],@"cdate")];
            [[eo values] tvfk([_APP now],@"ldate")];
        }else{
            [[eo values] tvfk([_APP today],@"cdate")];
            [[eo values] tvfk([_APP today],@"ldate")];
        }
    }

    // alle int-Felder auf 0;///
    // nicht nur Felder in DB damit insb. numerische parameter Felder initialsiert werden.
    allAttr = [myTable plainAttributes];
    for(i=0,j=[allAttr count];i<j;i++){
        PBDDAttribute *pba = [allAttr oai:i];
        NSString *initialValue = [pba initialValue];
        NSString *dbName = [pba dbName];

// f. debug           if([dbName iE:@"teilanzahl"])LOGS(([NSSWF @"%@ = %@",dbName,initialValue]));
        if(FILLED(initialValue)){
            if([initialValue iE:@"$today"])initialValue = [_APP today];
            if([initialValue iE:@"$now"])initialValue = [_APP now];
            if([initialValue iE:@"CURRENTUSER"])initialValue = CURRENTUSER;

            [[eo values] tvfk(initialValue,dbName)];
// f. debug               if([dbName iE:@"teilanzahl"])LOGS(([NSSWF @"%@ = %@ set",dbName,initialValue]));
           continue;
        }
// f. debug           if([dbName iE:@"teilanzahl"])LOGS(([NSSWF @"%@ = %@ not set",dbName,initialValue]));
        if(FILLED([pba expression]))continue;
        if([pba targetTyp]!=ATTVCSELOBJ)continue;
        dt = [pba dataTyp];
        if([pba isNumeric]){
            [[eo values] tvfk([NSString dbFromDouble:0.0 pba:pba],dbName)];
        }
        if([pba dataTyp] == DT_BOOL){
            if([dbName iE:NAME_OF_AKTIV_FLAG]){
                [[eo values] tvfk(SC_BOOL_TRUE,dbName)];
            }else{
                [[eo values] tvfk(SC_BOOL_FALSE,dbName)];
            }
        }
// hier koennte bei VLs noch der default-Entry gezogen werden; ist aber eigentlich durch den initialValue aus dem Modell schon abgebildet
    }
    return [eo autorelease];
}
- (BOOL)insertEO:(PBEO *)eo;
{
    //raw: ohne Pruefungen u. ohne Cache
    NSDictionary *eod;
    NSString *en;
    PBDDTable *myTable;
    PBDDAttribute *cdate;
    PBMySQLChannel *ac;
    double ts=0.0;
    BOOL needsAutokey = NO;
    PBEO *aneo=nil; //autonumber

    if([_APP collectIndexStatistics]){
        ts = [NSDate timeIntervalSinceReferenceDate];
    }

    if([_APP orbDebug]){
        LOGS(([NSSWF @"--> insertEO; %@.",[eo description]]));
    }
    if(!eo){
        LOGS(@"kein EO uebergeben");
        // PRINTCURRENTSTACK;
        return NO;
    }
    en = [eo entityName];
    myTable = [myDD tableNamed:en];
    if(!myTable){
        LOGS(([NSSWF @"keine solche Table:%@",en]));
        return NO;
    }

//nur bei autokey versorgen
    if([[myTable primaryKeyAttr]isAutoPK] && ![[eo values]ofk:@"no_auto_key"]){ 
//noch einen anderen auto-key typ definieren: z.B. auto_increment; wird von DB versorgt;
        NSString *s;
        EOQualifier *q;
        NSString *formatString;
        int newNumber; // max. 4 Mrd
        NSString *autonumberName;
        int retryCount;

        int l = [[myTable primaryKeyAttr]length];
        
// beide tables locken; falls lock nicht erhalten, return NO;
// autokey holen/anlegen, erhoehen; noch nicht updaten
        needsAutokey = YES;

        if(![self lockTables:[NSSWF @"%@, autonumber",en]]){
            LOGS(([NSSWF @"INSERT %@ failed; could not acquire lock for autonumber;",[eo description]]));
            return NO;
        }

        [parmDict setObject:eo forKey:@"p_eo"];
        [parmDict setObject:en forKey:@"p_autonumberName"];
        autonumberName = [[_APP executeScriptNamed:@"autonumberNameForEO" datasource:(PBWOEditor *)self parmDict:parmDict]ofk:@"p_autonumberName"]; //rewrite autonumberName

        q = [EOQualifier qualifierFromDict:[NSDictionary dwok:autonumberName,@"tablename",nil,nil]];
        if([q isKindOfClass:[EONothingQualifier class]]){
            LOGS(([NSSWF @"INSERT %@ failed; could create qualifier for autonumber;",[[NSDictionary dwok:autonumberName,@"tablename",nil,nil] description]]));
            [self unlockTables];
            return NO;
        }

        aneo = [[self getBasicEOs:q entityName:@"autonumber" offset:0 count:1  soa:nil]firstObject];

        if(!aneo){
            if(![myDD tableNamedCheap:autonumberName]){
                aneo = [self newAutoNumberEntryForTableNamed:autonumberName withLastKey:NO];
            }else{
                aneo = [self newAutoNumberEntryForTableNamed:autonumberName withLastKey:YES];
            }
        }else{
            if(![[aneo vfk:@"actualnumber"]intValue]){
                // bei 0 versuchen, bestehenden letzten Key zu ermitteln
                [aneo tvfk([self lastKeyForTableNamed:autonumberName],@"actualnumber")];
            }
        }
        if(!aneo){
            LOGS(([NSSWF @"konnte keinen neuen autonumber-Eintrag fuer %@ anlegen",autonumberName]));
            [self unlockTables];
            return NO;
        }

        formatString = [aneo vfk:@"formatstring"];
        if(!FILLED(formatString)){
// fuehrende nullen sind bloed
            formatString=@"%i";
        }else{
            formatString = [[NSCalendarDate date]descriptionWithCalendarFormat:formatString]; //falls %m, %d, %y, %Y drin sind
        }


        retryCount = 0;
        while(retryCount++ < 200){
            NSString *sql,*testKey;

            newNumber = ([[aneo vfk:@"actualnumber"]intValue]+1);

            [aneo tvfk((NSS(newNumber)),@"actualnumber")];
        // hier noch nicht; erst wenn insert des Haupt-EO geklappt hat    UPDAT(aneo);
            
        //autonumberForEO script kann p_autonumber modifizieren
            [parmDict setObject:autonumberName forKey:@"p_autonumberName"];
            [parmDict setObject:[NSSWF formatString,newNumber] forKey:@"p_autonumber"];

            s = [[_APP executeScriptNamed:@"autonumberForEO" datasource:(PBWOEditor *)self parmDict:parmDict]ofk:@"p_autonumber"];
            if(l<[s length]){
                LOGS(([NSSWF @"primaryKey gekuerzt: %@ %@",en,s]));
                s = [s substringFromIndex:[s length]-l];
                LOGS(s);
            }
// hier  pruefen, ob Nr. schon vergeben und solange hochzaehlen bis freie gefunden; um Luecken zu fuellen
            sql = [NSSWF @"select %@ from %@ where %@ = '%@' limit 1",[eo primaryKeyName],[eo entityName],[eo primaryKeyName],s];
            testKey = singleValueSQL(sql);
            if(!FILLED(testKey))break;
            LOGS(([NSSWF TRANSLATION(@"%@: %@ bereits in Verwendung, suche weiter"),[eo entityName],s]));
        }
        if(retryCount >= 200){
            LOGS(([NSSWF @"INSERT failed; %@ keine freie Nr. gefunden nach 200 Versuchen",[eo description]]));
            return NO;
        }

        [eo setPrimaryKey:s];
    }

    if(!FILLED([eo primaryKey])){
        LOGS(([NSSWF @"INSERT %@ failed; no primaryKey",[eo description]]));
        if(needsAutokey)[self unlockTables];
        return NO;
    }

    [[eo values] tvfk(CURRENTUSER,@"cuser")];
    [[eo values] tvfk(CURRENTUSER,@"luser")];
    cdate = [myTable plainAttrNamed:@"cdate"];
    if(cdate){
        if([cdate dataTyp]==DT_DATETIME){
            [[eo values] tvfk([_APP now],@"cdate")];
            [[eo values] tvfk([_APP now],@"ldate")];
        }else{
            [[eo values] tvfk([_APP today],@"cdate")];
            [[eo values] tvfk([_APP today],@"ldate")];
        }
    }

    eod = [self dictFromEO:eo forKeys:nil]; //alle Werte
    if(![eod count]){
        LOGS(([NSSWF @"INSERT %@ failed; conversion to dictionary failed;",[eo description]]));
        if(needsAutokey)[self unlockTables];
        return NO;
    }
    if(!(ac = [self freshChannel])){
        LOGS(([NSSWF @"INSERT %@ failed; no channel;",[eo description]]));
        if(needsAutokey)[self unlockTables];
        return NO;
    }
    if(![ac insertRow:eod forTable:myTable]){
        LOGS(([NSSWF @"INSERT %@ failed; (s. vorherige Meldung)",[eo description]]));
        if(needsAutokey)[self unlockTables];
        return NO;
    }

// hier jetzt auch autokey satz updaten, falls benoetigt
// unlock tables
    if(needsAutokey){
        UPDAT(aneo);
        [self unlockTables];
    }

// wenn es ein serial ist, den vergebenen Wert holen
    if([[myTable primaryKeyAttr]isSerialPK]){
        if([[eo primaryKey]intValue] == 0)[eo setPrimaryKey:singleValueSQL(@"select LAST_INSERT_ID()")];
    }

// ist der primaryKey numerisch und gibt es ein _wcs Feld, dieses auch versorgen und updaten
    if([myTable hasWCS]){
        [eo tvfk([eo primaryKey],([NSSWF @"%@_wcs",[myTable primaryKeyName]]))];
        UPDAT(eo);
    }
    [eo setDescri:nil];
    [eo setWasFetched:YES];
    if([_APP collectIndexStatistics]){
        ts = [NSDate timeIntervalSinceReferenceDate] - ts;
        if(ts>1.0)LOGS(([NSSWF @"%03f sec. ### insertEO: table %@ ",ts,[eo entityName]]));
        HANDLE_SLOWDB;
    }
    return YES;
}
- (BOOL)deleteEOsQ:(EOQualifier *)q entityName:(NSString *)en;
{
    PBDDTable *myTable;
    PBMySQLChannel *ac;
    double ts=0.0;

    if([_APP collectIndexStatistics]){
        ts = [NSDate timeIntervalSinceReferenceDate];
    }

    myTable = [myDD tableNamed:en];
    if(!myTable){
        LOGS(([NSSWF @"deleteEOsQ %@ failed; no table;",en]));
        return NO;
    }
    if([_APP orbDebug]){
        LOGS(([NSSWF @"--> deleteEOsQ %@ en:%@",[q description],en]));
    }
    if(!(ac = [self freshChannel])){
        LOGS(([NSSWF @"deleteEOsQ %@ failed; no channel;",en]));
        return NO;
    }
    if(![ac deleteRowsDescribedByQualifier:q forTable:myTable]){
        LOGS(([NSSWF @"deleteEOsQ %@ failed; (s. vorherige Meldung)",en]));
        return NO;
    }
    if([_APP collectIndexStatistics]){
        ts = [NSDate timeIntervalSinceReferenceDate] - ts;
        if(ts>1.0)LOGS(([NSSWF @"%03f sec. ### deleteEOsQ:%@ table %@ ",ts,[q description],en]));
        HANDLE_SLOWDB;
    }

    return YES;
}
- (BOOL)updateEO:(PBEO *)eo;
{
    PBDDTable *myTable;
    NSString *en,*pk;
    NSDictionary *eod;
    PBMySQLChannel *ac;
    double ts=0.0;
    PBDDAttribute *ldate;

    if([_APP collectIndexStatistics]){
        ts = [NSDate timeIntervalSinceReferenceDate];
    }

    if([_APP orbDebug]){
//        LOGS(([NSSWF @"--> updateEO; %@.",[eo description]]));
    }
    if(!eo){
        LOGS(@"kein EO uebergeben.");
        // PRINTCURRENTSTACK;
        return NO;
    }
    en = [eo entityName];
    myTable = [myDD tableNamed:en];
    if(!myTable){
        LOGS(([NSSWF @"UPDAT %@ failed; no table",[eo description]]));
        return NO;
    }
    if(![[eo oldValues]count])return YES;	//nix zu tun
    
//statistic felder erst hier versorgen, wenn feststeht, dass auch andere Felder geaendert
    if(![[eo oldValues]ofk:@"luser"])[eo tvfk(CURRENTUSER,@"luser")]; //wenn nicht explizit geaendert...

    ldate = [myTable plainAttrNamed:@"ldate"];
    if(ldate){
        if([ldate dataTyp]==DT_DATETIME){
            [eo tvfk([_APP now],@"ldate")];
        }else{
            [eo tvfk([_APP today],@"ldate")];
        }
    }

    eod = [self dictFromEO:eo forKeys:[eo oldValues]];
    if(![eod count])return YES; //nix zu tun

    if(!(ac = [self freshChannel])){
        LOGS(([NSSWF @"UPDAT %@ failed; no channel",[eo description]]));
        return NO;
    }
    pk = [eo primaryKey];
    if(![ac updateRow:eod forTable:myTable pk:[pk mysqlEscapedString]]){
        LOGS(([NSSWF @"UPDAT %@ failed; (s. vorherige Meldung)",[eo description]]));
        return NO;
    }
    [[eo changedValues]removeAllObjects];
    [[eo changedValues]addEntriesFromDictionary:[eo oldValues]]; //damit man in didUpdate feststellen kann, was sich geaendert hat
    [[eo oldValues]removeAllObjects];
    [eo setDescri:nil];
    if([_APP collectIndexStatistics]){
        ts = [NSDate timeIntervalSinceReferenceDate] - ts;
        if(ts>1.0)LOGS(([NSSWF @"%03f sec. ### UpdateEO: table %@ pk %@  ",ts,[eo entityName],[eo primaryKey]]));
        HANDLE_SLOWDB;
    }
    return YES;
}
//array deleten
- (BOOL)deleteEOs:(NSArray *)a;
{
    double ts=0.0;
    NSString *en;
    EOQualifier *q;
    PBDDTable *myTable;
    NSMutableArray *qa = [NSMutableArray arrayWithCapacity:100];
    int i,j;
    PBEO *eo;
    PBMySQLChannel *ac;

//sortenrein
    if([_APP collectIndexStatistics]){
        ts = [NSDate timeIntervalSinceReferenceDate];
    }


    if(![a count])return YES;
    en = [[a firstObject]entityName];
    myTable = [myDD tableNamed:en];
    if(!myTable){
        return NO;
    }
    if([a count]==1){
        return [self deleteEO:[a firstObject]];
    }
    if([_APP orbDebug]){
        LOGS(([NSSWF @"--> delete %i EOs; %@.",[a count]]));
    }
    if(!(ac = [self freshChannel]))return NO;
    i=0;j=[a count]-1;
    while(YES){
        eo = [a oai:i];

        [qa addObject:[eo pkq]];

        if((!(i% 100) || i==j) && i){	//i==0 wird oben abgefangen: einzel-delete
            q = [[[EOOrQualifier alloc]initWithQualifierArray:qa]autorelease];
            [qa removeAllObjects];
            if(![ac deleteRowsDescribedByQualifier:q forTable:myTable]){
                return NO;
            }
        }
        i++;
        if(i>j)break;
    }

    if([_APP collectIndexStatistics]){
        ts = [NSDate timeIntervalSinceReferenceDate] - ts;
        if(ts>1.0)LOGS(([NSSWF @"%03f sec. ### deleteEOs: table %@ ",ts,en]));
        HANDLE_SLOWDB;
    }
    return YES;
}
- (BOOL)deleteEO:(PBEO *)eo;
{
    double ts=0.0;
    EOQualifier *q;
    NSString *en;
    PBDDTable *myTable;
    PBMySQLChannel *ac;

    if([_APP collectIndexStatistics]){
        ts = [NSDate timeIntervalSinceReferenceDate];
    }

    if([_APP orbDebug]){
        LOGS(([NSSWF @"--> deleteEO; %@.",[eo description]]));
    }
    if(!eo){
        LOGS(@"kein EO uebergeben.");
        // PRINTCURRENTSTACK;
        return NO;
    }
    en = [eo entityName];
    myTable = [myDD tableNamed:en];
    if(!myTable){
        LOGS(([NSSWF @"keine solche Entity:%@",en]));
        return NO;
    }
    if(!(ac = [self freshChannel]))return NO;
    q = [eo pkq];
    if(![ac deleteRowsDescribedByQualifier:q forTable:myTable]){
        LOGS(([NSSWF @"DELET %@ failed; (s. vorherige Meldung)",[eo description]]));
        return NO;
    }

    if([_APP collectIndexStatistics]){
        ts = [NSDate timeIntervalSinceReferenceDate] - ts;
        if(ts>1.0)LOGS(([NSSWF @"%03f sec. ### deleteEO: table %@ pk %@  ",ts,[eo entityName],[eo primaryKey]]));
        HANDLE_SLOWDB;
    }
    return YES;
}
///////////////////////////////////////////////////////////////////////////////////////////
//	channel handling
///////////////////////////////////////////////////////////////////////////////////////////
- (void)closeAllChannels;
{
    int i,j;
    PBMySQLChannel *ac;

    for(i=0,j=[channelPool count];i<j;i++){
        ac = [channelPool oai:i];
        if([ac isOpen]){
            if([ac isFetchInProgress])[ac cancelFetch];
            [ac closeChannel];
        }
    }
    [channelPool removeAllObjects];
}
- (PBMySQLChannel *)freshChannel;
{
    //alle channelPool durchschauen; wenn einer geclosed ist, open u. verwenden;
    // wenn einer !fetchin Progress, verwenden
    //der channel muss danach sofoert verwendet werden, sonst wird er nochmal vergeben...
    PBMySQLChannel *c;
    int i,j;

 //   LOGS(([NSSWF @"channelpool count =%i",j]));
// in geschlossenen Channels einen wiederverwertbaren suchen
// rueckwaerts, damit der Index richtig bleibt
    for(i=[channelPool count]-1;i>=0;i--){
        c = [channelPool objectAtIndex:i];
        if([c isOpen])continue;
        if([c dbNr] != dbNr)continue;
        if(![c openChannel]){
            LOGS(@"konnte alten Adaptorchannel nicht oeffnen.");
            [channelPool removeObjectAtIndex:i]; //mit Index;
            continue;
        }
//        LOGS(([NSSWF @"returning reopened channel; channelpool count =%i",[channelPool count]]));
        return c;	
    }

// in offenen channels einen wiederverwertbaren suchen
    for(i=0,j=[channelPool count];i<j;i++){
        c = [channelPool objectAtIndex:i];
        if([c dbNr] != dbNr)continue;
        if(![c isFetchInProgress]){
 //           LOGS(([NSSWF @"reuseing open channel; channelpool count =%i",[channelPool count]]));
            return c;
        }
    }

    c = [[PBMySQLChannel alloc]init];
    if(!c){
        LOGS(@"keinen Adaptorchannel erhalten.");
        LOGS(([NSSWF @"channelpool count =%i",j]));
        return nil;
    }
    if(![c openChannel]){
        LOGS(@"konnte neuen Adaptorchannel nicht oeffnen.");
        LOGS(([NSSWF @"channelpool count =%i",[channelPool count]]));
        return nil;
    }
    [channelPool addObject:c];
    // LOGS(NSS([channelPool count]));
    return c;	
}
///////////////////////////////////////////////////////////////////////////////////////////
//	SQL related
///////////////////////////////////////////////////////////////////////////////////////////
- (NSMutableString *)sqlValuesStringFromDict:(NSDictionary *)d forTable:(PBDDTable *)t;
{
    //dict schon mit dictFromEO vorbereitet: nur noch db-Attribute, nur noch delta bei update, alle werte im db-Format
    //hier nur noch quoten und escapen;
    //pass f. insert und update
    NSArray *a,*a1;
    int i,j;
    NSMutableString *ms = [NSMutableString stringWithCapacity:1024];
    PBDDAttribute *pba;
    NSString *colName,*colValue;
    
    if(!d || !t)return nil;
    [ms setString:EON];
    a = [d allKeys]; a1 = [d allValues];
    for(i=0,j=[a count];i<j;i++){
        colName = [a oai:i];
        colValue = [a1 oai:i];
        pba = [t plainAttrNamed:colName];
        if(!pba){
            LOGS(([NSSWF @"unbekannte Spalte: %@",colName]));
            continue;
        }
        if(!FILLED(colValue)){
    //        colValue=@"NULL";
            colValue=@"\"\""; //immer leer wg. uniq-Feldern; 
        }else{
            if([pba dataTyp]==DT_CHAR){
                colValue = [colValue mysqlEscapedString];
            }
            colValue = [NSString stringWithFormat:@"\"%@\"",colValue]; //immer quoten; zahlen eigentlich nicht
        }
        if(i)[ms appendString:@", "];
        [ms appendFormat:@"%@=%@",colName,colValue];
    }
    return ms;
}
- (NSString *)whereClauseFrom:(EOQualifier *)q forTable:(PBDDTable *)t;
{
    NSArray *a;
    EOQualifier *q1;
    int i,j;
    NSString *s,*whereClause;
    NSMutableString *ms = [NSMutableString stringWithCapacity:100];


    // table wird vielleicht mal benoetigt f. formatierungen
    // eigentlich sollte der value jedoch richtig drinstehen, auch schon gequoted
    if(!FILLED(q))return EON;

    if([q isKindOfClass:[PBSQLQualifier class]]){
        whereClause = [(PBSQLQualifier *)q string];
//        if([_APP orbDebug])LOGS(whereClause);
        return whereClause;
    }
    
    if([q isKindOfClass:[EOOrQualifier class]]){
        a = [(EOOrQualifier *)q qualifiers];
        for(i=0,j=[a count];i<j;i++){
            q1 = [a oai:i];
            s = [self whereClauseFrom:q1 forTable:t];
            if(FILLED(s)){
                if(FILLED(ms))[ms appendString:@" OR "];
                [ms appendFormat:@"(%@)",s];
            }else{
                // ist ein Qualifier nicht gefuellt, ist der ganze ausdruck auf jedenfall wahr;
                return EON;
            }
        }
        whereClause = ms;
//nicht so interessant        if([_APP orbDebug])LOGS(whereClause);
        return whereClause;
    }

    if([q isKindOfClass:[EOAndQualifier class]]){
        a = [(EOAndQualifier *)q qualifiers];
        for(i=0,j=[a count];i<j;i++){
            q1 = [a oai:i];
            s = [self whereClauseFrom:q1 forTable:t];
            if(FILLED(s)){
                if(FILLED(ms))[ms appendString:@" AND "];
                [ms appendFormat:@"(%@)",s];
            }
        }
        whereClause = ms;
//        if([_APP orbDebug])LOGS(whereClause);
        return whereClause;
    }

    if([q isKindOfClass:[EONotQualifier class]]){
        q1 = [(EONotQualifier *)q qualifier];
        whereClause = [NSSWF @" NOT (%@)",[self whereClauseFrom:q1 forTable:t]];
//        if([_APP orbDebug])LOGS(whereClause);
        return whereClause;

    }

    if([q isKindOfClass:[EOAllQualifier class]]){
        whereClause =  @"(1 = 1)";
//        if([_APP orbDebug])LOGS(whereClause);
        return whereClause;
    }

    if([q isKindOfClass:[EONothingQualifier class]]){
        whereClause =  @"(1 = 2)";
//        if([_APP orbDebug])LOGS(whereClause);
        return whereClause;
    }

    if([q isKindOfClass:[EOKeyComparisonQualifier class]]){
        s = [EOQualifier stringForOperatorSelector:[(EOKeyComparisonQualifier *)q selector]];
        whereClause =  [NSSWF @"(%@ %@ %@)",[(EOKeyComparisonQualifier *)q leftKey],s,[(EOKeyComparisonQualifier *)q rightKey]];
//        if([_APP orbDebug])LOGS(whereClause);
        return whereClause;
    }

    if([q isKindOfClass:[PBSQLQualifier class]]){
        whereClause =  [(PBSQLQualifier *)q string];
//        if([_APP orbDebug])LOGS(whereClause);
        return whereClause;
    }

    if([q isKindOfClass:[EOKeyValueQualifier class]]){
        NSString *value = [(EOKeyValueQualifier *)q value];
        NSString *key = [(EOKeyValueQualifier *)q key];
        SEL sel = [(EOKeyValueQualifier *)q selector];
        int dt = [[t plainAttrNamed:key] dataTyp];

        s = [EOQualifier stringForOperatorSelector:sel];

        if([value isKindOfClass:[EONull class]]){
            value = @"NULL";
            if([s iE:@"="]){
                s = @"is";
            }else{
                s = @"is not";
            }
            return [NSSWF @"(%@ %@ %@)",key,s,value];
        }
        //bei like die wildcards machen; escape mit backslash
//wg. GnuStep: [s iE:@"like"]
        if(sel_eq(sel, EOQualifierOperatorLike) || sel_eq(sel,EOQualifierOperatorCaseInsensitiveLike) || [s iE:@"like"]){
            // * -> %, ? -> _ wenn nicht escaped mit backslash
            if(dt == DT_CHAR){
                value = [self wildCardStringFrom:value];
                whereClause =   [NSSWF @"(%@ %@ '%@')",key,s,[value mysqlEscapedString]];
            }else{
                LOGS(@"nur character-type Felder duerfen mit LIKE verglichen werden");
                whereClause =    EON;
            }
//            if([_APP orbDebug])LOGS(whereClause);
            return whereClause;
        }
        
        //kein like; value rewriting
        switch(dt){
            case DT_CHAR:
            case DT_BOOL:
                break;
            case DT_DATE:
                if([value iE:EON]){ //entspricht NULL
                    value = @"0000-00-00";
                }else{
                    [GPBD setDBString:value];
                    value = [GPBD dateAsMysqlDBString];
                }
                break;
            case DT_DATETIME:
                if([value iE:@""]){ //entspricht NULL
                    value = @"0000-00-00 00:00:00";
                }else{
                    [GPBD setDBString:value];
                    value = [GPBD dateAsMysqlDBDTString];
                }
                break;
            case DT_MONEY:
            case DT_FLOAT:
            case DT_INT:
                if([value iE:EON]){ //entspricht NULL
                    value = @"0";
                }
        }

        whereClause = [NSSWF @"(%@ %@ '%@')",key,s,[value mysqlEscapedString]];
//        if([_APP orbDebug])LOGS(whereClause);
        return whereClause;
    }
    LOGS(@"unbekannter Qualifier-Typ.");
    whereClause = EON;
    return whereClause;
}
- (NSString *)orderbyClauseFrom:(NSArray *)soa forTable:(PBDDTable *)t;
{
    NSMutableString *obyc;
    int i,j=[soa count];
    EOSortOrdering *eos;
    NSString *k,*ad;
    SEL sel;
    
    if(!j)return EON;
    obyc = [NSMutableString stringWithCapacity:100];
    [obyc setString:@"order by "];

    for(i=0;i<j;i++){
        eos = [soa oai:i];
        k = [eos key];
        sel = [eos selector];
        if(sel_eq(sel,EOCompareAscending) || sel_eq(sel,@selector(compareNumeric:)) || sel_eq(sel,@selector(compare:)) || sel_eq(sel,@selector(compareCaseInsensitive:))){
            ad = @"ASC";
        }else{
            ad = @"DESC";
        }
        if(i)[obyc appendString:@", "];
        [obyc appendFormat:@"%@ %@ ",k,ad];
    }

    return obyc; 
}
- (NSString *)wildCardStringFrom:(NSString *)s;
{
    BOOL isEscaped = NO;
    int i,j;
    NSMutableString *ms = [NSMutableString stringWithCapacity:20];
    unichar c=0;

    [ms setString:EON];
    for(i=0,j=[s length];i<j;i++){
        c = [s characterAtIndex:i];
        if(!isEscaped){
            switch(c){
                case '*':
                    [ms appendString:@"%"];
                    continue;
                case '?':
                    [ms appendString:@"_"];
                    continue;
                case '%':
                    [ms appendString:@"\\%"];
                    continue;
                case '_':
                    [ms appendString:@"\\_"];
                    continue;
                case '\\':
                    isEscaped = YES;
                    continue;
             }
        }else{
            isEscaped = NO;
        }
        [ms appendString:[NSString stringWithCharacters:&c length:1]];
    }
    return (NSString *)ms;
}

- (NSString *)sqlStringFor:(PBDate *)pbd;
{
    return [pbd dateAsMysqlDBString];
}

- (NSString *)sqlFormatCt:(PBDDTable *)t;
{
    return [self sqlFormatCt:t tn:[t dbName] temp:NO];
}
- (NSString *)sqlFormatCt:(PBDDTable *)t tn:(NSString *)tn temp:(BOOL)temp;
{
    static NSMutableString *s;
    NSString *constraint,*temporary;
    int	i,j,rac=0;
    NSArray	*a;
    PBDDAttribute	*pba;

    if(!s){
        s = [[NSMutableString alloc]initWithCapacity:1000];
    }
    if(temp){
        temporary = @"temporary";
    }else{
        temporary = EON;
    }
// vorher muss im DD reorg gemacht worden sein;
    [s setString:EON];
    if([t isRealTable]){
        a = ATisDB(t);
        [s appendFormat:@"create %@ table %@ (\n",temporary,[tn lowercaseString]];
        constraint = EON;
        for(i=0,j=[a count];i<j;i++){
            pba = [a oai:i];
            if(rac)[s appendString:@",\n"];
            [s appendString:[self sqlFormatCANew:pba]];
            
            if([pba isPK]){
                if([pba isSerialPK]){
                    [s appendFormat:@",\nprimary key(%@)",[[pba dbName]lowercaseString]];
                }else{
                    constraint = [NSSWF @"alter table %@ add unique index %@_pk (%@);",[tn lowercaseString],[tn lowercaseString],[[pba dbName]lowercaseString]];
                }
            }
            rac++;
        }
        [s appendString:@");\n"];
        [s appendString:constraint];
        [s appendString:@"\n"];
        return s;
    }else{
        return EON;
    }
}
- (NSString *)sqlFormatCANew:(PBDDAttribute *)pba;
{
    static NSMutableString *s;
    NSString *atn = [[pba dbName]lowercaseString],*dbTyp =  @"CHAR(1)";

    if([pba isSerialPK]){
        return [NSSWF @"%@ INTEGER NOT NULL auto_increment",atn];
    }
    if(!s){
        s = [[NSMutableString alloc]initWithCapacity:100];
    }
    [s setString:EON];
    switch([pba dataTyp]){
        case DT_CHAR:
            if([pba length]<4){
                dbTyp = [NSSWF @"char(%i)",[pba length]];
            }else if([pba length]<256){
                dbTyp = [NSSWF @"varchar(%i)",[pba length]];
            }else{
                dbTyp =  [NSSWF @"text"];
            }
            break;
        case DT_DATE:
            dbTyp =  @"DATE";
            break;
        case DT_DATETIME:
            dbTyp =  @"DATETIME";
            break;
        case DT_MONEY:
            dbTyp =  @"DECIMAL(11,2)"; //platz f. vorzeichen
            break;
        case DT_FLOAT:
            dbTyp =  [NSSWF @"DECIMAL(%i,%i)",[pba length]+1,[pba nak]]; //
            break;
        case DT_INT:
            dbTyp =  @"INTEGER";
            break;
        case DT_BOOL:
            dbTyp =  @"CHAR(1)";
            break;
    }

    [s appendFormat:@"    %@ %@",atn,dbTyp];
    if([pba isPK]){
#warning sollte man immer machen, bringt bessere performance lt. MySQL
        [s appendString:@" NOT NULL"];
    }
    return s;
}
- (void)sqlRunStatement:(NSString *)s withFetch:(BOOL)yn;
{
    NSDictionary *row=nil;
    NSArray *a,*rs;
    int i,j;
    id ac;

    if(!FILLED(s))return;
    if(!(ac = [self freshChannel]))return;
    
    a = [s componentsSeparatedByString:@";\n"];

    for(i=0,j=[a count];i<j;i++){
        s = [a oai:i];
        if(!FILLED(s))continue;
        if([s length]<=80){
            LOGS(s);
        }else{
            LOGS(([[s secureSubstringToIndex:80]sbas:@"..."]));
        }
        
        [ac evaluateSQL:s];
        if(yn){
            while([ac isFetchInProgress]){
                rs = [ac describeResults]; //falls non-rectacular result set
                [ac setSelectedAttributes:rs];
                while ((row = [ac fetchRow])) {
                    LOGS([row description]);
                }
            }
        }else{
            [ac cancelFetch];
        }
    }
}
- (void)readArialuni_cmap;
{
//		      Char 0F50 -> Index 2334
    NSString *s = [NSSWCOF [NSSWF @"%@/arialuni_cmap.txt",GLOBALCONFIGPATH]];
    NSArray *a = [s componentsSeparatedByString:@"\n"];
    int i,j;

    for(i=0,j=65535;i<j;i++)arialuni_cmap[i]=0;
    for(i=0,j=[a count];i<j;i++){
        unsigned short uni;
        s = [a oai:i];
        uni = [[[s secureSubstringFromIndex:13]secureSubstringToIndex:4]hexValue];
        arialuni_cmap[uni]=[[s secureSubstringFromIndex:27]intValue];
    }
}
- (void)readArialuni_hmtx;
{
//    17896. advWid: 2048, LSdBear: 113
// die hmtx sind nach cid organisiert und lueckenlos
    NSString *s = [NSSWCOF [NSSWF @"%@/arialuni_hmtx.txt",GLOBALCONFIGPATH]];
    NSArray *a = [s componentsSeparatedByString:@"\n"];
    NSString *sep = @". advWid: ";
    int i,j;

    for(i=0,j=65535;i<j;i++)arialuni_hmtx[i]=1000;
    for(i=0,j=[a count];i<j;i++){
        NSArray *a1 = [[a oai:i]componentsSeparatedByString:sep];

        if([a1 count]!=2)continue;
        arialuni_hmtx[i]=(unsigned short)[[[[[a1 oai:1]stringWithoutSpace]componentsSeparatedByString:@","]firstObject]floatValue] / 2.023; //ist viel zu breit
    }
}
- (NSString *)cidStringFrom:(NSString *)s;
{
// die unicode characters in cid hex-zahlen umwandeln u. < > drumrum fuer PDF
    int j = [s length];
    int i=0;
    NSMutableString *ms = [NSMutableString stringWithCapacity:(j * 4) + 3];

    [ms setString:@"<"];
    for(i=0,j=[s length];i<j;i++){
        unsigned short cid=0;
        unichar uni = [s characterAtIndex:i];
        cid = arialuni_cmap[uni];
        if(cid)[ms appendString:[NSSWF @"%04x",cid]]; //gibt sonst kaestchen
    }
    [ms appendString:@">"];
    return ms;
}
- (unsigned short)cidForUnichar:(unichar)uni;
{
    return arialuni_cmap[uni];
}
- (unsigned short)widthForCid:(unsigned short)cid;
{
    return arialuni_hmtx[cid];
}
- (unsigned short)widthForUnichar:(unichar)uni;
{
    return [self widthForCid:[self cidForUnichar:uni]];
}

@end

@implementation Application (CEOH)
// das hier wird auch noch aufgeloest und in Scripts verlagert; 
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// methoden fuer verschiedene Tabellen
- (NSArray *)helementv3_directChildrenOf:(PBEO *)eo;
{
    return getEOsKnKv(@"helpdeskv3",@"parentelement",[eo primaryKey]);
}
- (void)helementv3_genPrimKey:(PBEO *)eo inParentEO:(PBEO *)peo;
{
    if(!peo){ //root element
        return;
    }else{ //innerhalb parent den naechsten schluessel
        NSArray *a = [[self helementv3_directChildrenOf:peo]valuesForKey:@"pid"];
        int i,j,lk=0;

        if(!a){
            [eo tvfk(([NSSWF @"%@.0",[peo primaryKey]]),@"pid")]; //erstes element unter diesem parent
            return;
        }
        for(i=0,j=[a count];i<j;i++){  //aeltestes Geschwister festsellen
            NSString *s = [a oai:i];
            int lk1;

            lk1 = [[[s componentsSeparatedByString:@"."]lastObject]intValue];
            if(lk1 > lk)lk = lk1;
        }
        lk++;
        [eo tvfk(([NSSWF @"%@.%i",[peo primaryKey],lk]),@"pid")];
    }
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (BOOL)document_isJpg:(PBEO *)document;
{
    NSString *mt = [document vfk:@"mimetype"];

    if(![mt iE:@"image/jpeg"])return NO;
    return YES;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (NSArray *)zuordnungen_establishArrayZuord:(NSArray *)a1 :(NSArray *)a2 grund:(NSString *)grund inc:(BOOL)inc bemerk:(NSString *)bemerk;
{
    int i,j,ii,jj,ic=0,fc=0;
    PBEO *eo1,*eo2;
    NSString *s;
    LMA;

    if(!a1 || !a2)return nil;
    if(([a1 count] * [a2 count]) > 1000){
        LOGI(TRANSLATION(@"mehr als 1000 Zuordnungen auf einmal ist zuviel."));
        return nil;
    }
    for(i=0,j=[a1 count];i<j;i++){
        eo1 = [a1 oai:i];
        for(ii=0,jj=[a2 count];ii<jj;ii++){
            eo2 = [a2 oai:ii];
            if([eo1 iE:eo2])continue;
            s = [self zuordnungen_establishZuord:eo1 :eo2 grund:grund inc:inc bemerk:bemerk];
            if(s){
                [lma addObject:s]; //der prim.key
                ic++;
            }else{
                fc++;
            }
        }
    }
    LOGI(([NSSWF TRANSLATION(@"%i Zuordnungen erstellt/verstaerkt/vorhanden, %i fehlgeschlagen."),ic,fc]));
    return lma;
}
- (NSString *)zuordnungen_establishZuord:(PBEO *)eo1 :(PBEO *)eo2 grund:(NSString *)grund inc:(BOOL)inc bemerk:(NSString *)bemerk;
{
    PBEO *z;
    EOQualifier *q;

    if(!eo1 || !eo2)return nil;
    if([eo1 iE:eo2])return nil;
    if(!bemerk)bemerk = EON;

    if(!grund)grund = @"0";
    q = [EOQualifier qualifierWithQualifierFormat:@"((pks = %@ and tablenames = %@ and pkt = %@ and tablenamet = %@) or (pkt = %@ and tablenamet = %@ and pks = %@ and tablenames = %@))",[eo1 primaryKey],[eo1 entityName],[eo2 primaryKey],[eo2 entityName],[eo1 primaryKey],[eo1 entityName],[eo2 primaryKey],[eo2 entityName]];

    if((z = getEOQ(@"zuordnungen",q))){
        if(inc){
            [_APP evaluateSQL:[NSSWF @"update zuordnungen set staerke = staerke + 1 where pid = '%@'",[z primaryKey]]];

        }
    }else{
        z = NEW_EO(@"zuordnungen");
        [z tvfk(@"1",@"staerke")];
        [z tvfk(bemerk,@"bemerkung")];
        [z tvfk(grund,@"grund")];
        [z tvfk([eo1 primaryKey],@"pks")];
        [z tvfk([eo2 primaryKey],@"pkt")];
        [z tvfk([eo1 entityName],@"tablenames")];
        [z tvfk([eo2 entityName],@"tablenamet")];
        INSRT(z);
        [parmDict setObject:z forKey:@"p_eo"];
        [_APP executeScriptNamed:@"_Application/zuordnungen/didInsert" datasource:(PBWOEditor *)self parmDict:parmDict];
    }
    return [z primaryKey];
}
- (void)flagsToGruppenstring:(PBEO *)eo;
{
// beim Speichern
    NSString *gs = @"";
    int i;

    for(i=0;i<ANZ_GRUPPEN;i++){
        NSString *gn = [NSSWF @"gruppe%i",i];

        if([[eo vfk:gn]iE:@"J"]){
            gs = [gs stringByAppendingString:@"x"];
        }else{
            gs = [gs stringByAppendingString:@"-"];
        }
    }
    [eo tvfk(gs,@"gruppenstring")];
}
- (void)gruppenstringToFlags:(PBEO *)eo;
{
// beim Laden
    NSString *gs = [eo vfk:@"gruppenstring"];
    int i;

    for(i=0;i<ANZ_GRUPPEN;i++){
        NSString *gn = [NSSWF @"gruppe%i",i];
        if([[[gs charAt:i]lowercaseString]iE:@"x"]){
            [eo tvfk(@"J",gn)];
        }else{
            [eo tvfk(@"N",gn)];
        }
    }
}
@end

@implementation Application (Webpublishing)
// Webpublisher
#define WEBBASEPATH @"Aprica"
#define WEBIMAGEDIR @"Aprica_webImages"

- (NSString *)titleFromFn:(NSString *)fn;
{
    NSRange r = [fn rangeOfString:@" "];
    NSString *s;

    if(r.length == 0){
        s = fn;
    }else{
        s = [fn substringFromIndex:r.location + r.length];
    }
    if([s hasSecureSuffix:@".txt"])s = [s stringWithoutSuffix:@".txt"];
    return s;
}
- (NSString *)section:(int)i;
{
    switch(i){
        case 0: return @"chapter";
        case 1: return @"section";
        case 2: return @"subsection";
        case 3: return @"subsubsection";
    }
    return @"subsubsection";
}
- (void)publishDirectory:(NSString *)dir;
{
    NSArray *a = [[myFM directoryContentsAtPath:dir]sortedArray];
    int i,j;
    LMA;
    LMAN(latexma);
    NSArray *a1;
    int i1,j1;
    NSString *imgPathFormatString = [NSSWF @"%@/web/Images/%%@",MANDANTPATH];
    NSString *imgURLBasePath = WEBIMAGEDIR;

    for(i=0,j=[a count];i<j;i++){
        NSString *fn = [a oai:i];
        NSString *completeFn = [dir stringByAppendingFormat:@"/%@",fn];
        NSDictionary *fa = [myFM fileAttributesAtPath:completeFn traverseLink:NO];


        if([[fa ofk:NSFileType]iE:NSFileTypeRegular] && [fn hasSecureSuffix:@".txt"] && ![fn hasSecurePrefix:@"_idee"]){ //
            BOOL addBR = NO;
            BOOL inTT = NO;
            BOOL inText = NO;
            BOOL inOL = NO;
            BOOL inUL = NO;
            PBEO *eo = NEW_EO(@"webpublisher");
            [eo tvfk(NSS(nestinglevel),@"nestinglevel")];
            [eo tvfk([self urlFromCompleteFn:completeFn],@"url")];
            [eo tvfk([self titleFromFn:fn],@"title")]; //bis zum ersten Space abschneiden; dient der Sortierung

#include "renderHTML.h"

            [eo tvfk([lma componentsJoinedByString:@""],@"content")];
            [eo tvfk(@"J",@"isfinal")];
            [h3eos addObject:eo];
            [allLATeX appendString:[NSSWF @"\n\\%@{%@}\n%@",[self section:gliederungsTiefe],[[eo vfk:@"title"]latexEscapedString],[latexma componentsJoinedByString:@" "]]];
            continue;
        }
        if([[fa ofk:NSFileType]iE:NSFileTypeDirectory] && ![fn iE:@"Images"]   && ![fn iE:@".svn"]){
            PBEO *eo = NEW_EO(@"webpublisher");
            [eo tvfk(NSS(nestinglevel),@"nestinglevel")];
            [eo tvfk([self urlFromCompleteFn:completeFn],@"url")];
            [eo tvfk([self titleFromFn:fn],@"title")];
            [eo tvfk(EON,@"content")];
            [h3eos addObject:eo];
            [allLATeX appendString:[NSSWF @"\n\\%@{%@}",[self section:gliederungsTiefe],[[eo vfk:@"title"]latexEscapedString]]];
           nestinglevel++;
            gliederungsTiefe++;	//wg. kompatib. zu renderHTML
            [self publishDirectory:completeFn];
            nestinglevel--;
            gliederungsTiefe--;
        }
    }
}
- (NSString *)urlFromCompleteFn:(NSString *)completeFn;
{
// Sortierzahl vorne weg; .txt -> .html
    NSString *hdprefix = [NSSWF @"%@/web",MANDANTPATH];
    NSArray *a = [[[completeFn withoutPrefix:hdprefix] stringWithForwardSlashes]componentsSeparatedByString:@"/"];
    int i,j;
    LMA;
    NSString *s;

    for(i=0,j=[a count];i<j;i++){
        NSRange r;

        s = [a oai:i];
        r = [s rangeOfString:@" "];

        if(r.length > 0){
            s = [s substringFromIndex:r.location + r.length];
        }
        if([s hasSecureSuffix:@".txt"])s = [[s stringWithoutSuffix:@".txt"]stringByAppendingString:@".html"];
        [lma addObject:s];
    }
    s = [lma componentsJoinedByString:@"/"];
    if(![s hasSecurePrefix:@"/"])s = [@"/" stringByAppendingString:s];
    return s;
}
- (NSString *)navLinesFor:(PBEO *)eo;
{
    LMA;
    int i,j;
    NSString *url = [eo vfk:@"url"];
    int nl = [[eo vfk:@"nestinglevel"]intValue];
    int currentNL = 0;
    BOOL increasing = YES;

    for(i=0,j=[h3eos count];i<j;i++){
        PBEO *eo1 = [h3eos oai:i];
        NSString *url1 = [eo1 vfk:@"url"];
        int nl1 = [[eo1 vfk:@"nestinglevel"]intValue];
        BOOL wasAdded = NO;

        if(nl1 == currentNL){
            [lma addObject:[self navLinkFor:eo1 eo:eo]];
            wasAdded = YES;
        }
        if(increasing){
            if([url hasSecurePrefix:url1]){
                if(!wasAdded)[lma addObject:[self navLinkFor:eo1 eo:eo]];
                currentNL++;
                if(currentNL > nl){
                    currentNL = nl;
                    increasing = NO;
                }
            }
        }else{
            if(nl1 < currentNL){
                [lma addObject:[self navLinkFor:eo1 eo:eo]];
                currentNL--;
            }
        }
    }
    return [lma componentsJoinedByString:@""];
}

- (NSString *)navLinkFor:(PBEO *)navEO eo:(PBEO *)eo;
{
    NSString *navLinkDir = @"<tr><td nowrap>%@ <a class=\"navLinkDir\" href=\"/%@%@\">&raquo; %@</a></td></tr>";
    NSString *navLinkFile = @"<tr><td nowrap>%@ <a class=\"navLinkFile\" href=\"/%@%@\">%@</a></td></tr>";
    NSString *navLinkSelf = @"<tr><td nowrap>%@ <a class=\"navLinkSelf\" href=\"/%@%@\">%@</a></td></tr>";
    NSString *nestingImage=@"";
    int nl = [[navEO vfk:@"nestinglevel"]intValue];

    if(nl){
        nestingImage = [NSSWF @"<img src=\"/%@/1a.gif\" alt=\"\" width=%i height=1 border=0>",WEBIMAGEDIR,nl*10];
    }

    if([[navEO vfk:@"url"]iE:[eo vfk:@"url"]]){
        return [NSSWF navLinkSelf,nestingImage,WEBBASEPATH,[[navEO vfk:@"url"]urlEncodedString],[navEO vfk:@"title"]];
    }
    if([[navEO vfk:@"isfinal"]iE:@"J"]){
        return [NSSWF navLinkFile,nestingImage,WEBBASEPATH,[[navEO vfk:@"url"]urlEncodedString],[navEO vfk:@"title"]];
    }else{
        return [NSSWF navLinkDir,nestingImage,WEBBASEPATH,[[navEO vfk:@"urlff"]urlEncodedString],[navEO vfk:@"title"]];
    }
}
- (void)publish;
{
    NSString *basePath = [NSSWF @"%@/web",MANDANTPATH];
    NSString *web_template,*web_template_path;
    int i,j;
    BOOL first = YES;

    if(![myFM fileExistsAtPath:basePath]){
        LOGI(([NSSWF TRANSLATION(@"basePath %@ nicht gefunden"),basePath]));
        return;
    }
    [allLATeX setString:[NSSWF [NSSWCOF [NSSWF @"%@/publish_template.tex",basePath]],basePath]];

    [h3eos removeAllObjects];
    nestinglevel = 0;
    gliederungsTiefe = 0;
    [self publishDirectory:basePath];
    [allLATeX appendString:@"\\end{document}"];
    [allLATeX writeToFileLatin1:[NSSWF @"%@/Webserver_Resources/web/Publish.tex",GLOBALCONFIGPATH]];
// rendern
// den urlff ermitteln
   for(i=0,j=[h3eos count];i<j;i++){
       PBEO *eo = [h3eos oai:i];
       int i1;

       if([[eo vfk:@"isfinal"]iE:@"J"])continue;
       for(i1=i+1;i1<j;i1++){
           PBEO *eo1 = [h3eos oai:i1];

           if([[eo1 vfk:@"isfinal"]iE:@"J"]){
               [eo tvfk([eo1 vfk:@"url"],@"urlff")];
               break;
           }

       }
   }
// template laden
   web_template_path = [NSSWF @"%@/web/web_template.html",MANDANTPATH];
   web_template = [NSSWCOF web_template_path];
   if(!FILLED(web_template)){
       LOGS(([NSSWF @"web_template %@ nicht gefunden",web_template_path]));
       return;
   }
// web-Directory clearen
   system([[NSSWF @"rm -rf %@/Webserver_Resources/web/%@/*",GLOBALCONFIGPATH,WEBBASEPATH]cString]);
    system([[NSSWF @"rm -rf %@/Webserver_Resources/web/%@",GLOBALCONFIGPATH,WEBIMAGEDIR]cString]);
    system([[NSSWF @"cp -rf %@/web/Images %@/Webserver_Resources/web/%@",MANDANTPATH,GLOBALCONFIGPATH,WEBIMAGEDIR]cString]);
    
// alle final eos verarbeiten; pro eo wird ein .html file geschrieben; dazu die Nav-Leiste kunstvoll generieren; alle Directories auf dem Weg anlegen
    for(i=0,j=[h3eos count];i<j;i++){
        PBEO *eo = [h3eos oai:i];
        NSString *htmlPath,*htmlFile;
        NSString *navLines;
        if([[eo vfk:@"isfinal"]iE:@"N"])continue;
        navLines = [self navLinesFor:eo];
        htmlFile = [NSSWF web_template,[eo vfk:@"url"],navLines,[eo vfk:@"title"],[eo vfk:@"content"]];
        htmlPath = [NSSWF @"%@/Webserver_Resources/web/%@/%@",GLOBALCONFIGPATH,WEBBASEPATH,[eo vfk:@"url"]];
        [myFM createAllDirsAtPath:[[htmlPath stringByDeletingLastPathComponent]stringWithForwardSlashes]];
        [htmlFile WTF:htmlPath];
// erstes File als index.html wegschreiben
        if(first){
            [htmlFile WTF:[NSSWF @"%@/Webserver_Resources/web/%@/index.html",GLOBALCONFIGPATH,WEBBASEPATH]];
            first = NO;
        }
    }
//ins htdocs kopieren
    system([[NSSWF @"rm -rf /Programme/Apache/htdocs/%@",WEBBASEPATH]cString]);
    system([[NSSWF @"rm -rf /Programme/Apache/htdocs/%@",WEBIMAGEDIR]cString]);
    system([[NSSWF @"cp -rf %@/Webserver_Resources/web/%@ /Programme/Apache/htdocs",GLOBALCONFIGPATH,WEBBASEPATH]cString]);
    system([[NSSWF @"cp -rf %@/Webserver_Resources/web/%@ /Programme/Apache/htdocs",GLOBALCONFIGPATH,WEBIMAGEDIR]cString]);
}

@end

@implementation Application (XML)
- (NSString *)encodeObject:o withDocType:(NSString *)docType;
{
// o muss ein Dictionary oder Array sein
// docType ist ein frei waehlbarer name

    NSMutableString *ms = [NSMutableString stringWithCapacity:1000];

    [ms setString:@"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"];
    if([o isKindOfClass:[NSDictionary class]]){
        [self encodeDictionary:o inXML:ms forName:docType  nestingLevel:0];
    }else if([o isKindOfClass:[NSArray class]]){
        [self encodeArray:o inXML:ms forName:docType  nestingLevel:0];
    }else{
        LOGS(@"falsche Klasse; muss Dictionary oder Array sein");
        return @"";
    }
    return ms;
}
- (void)encodeDictionary:(NSDictionary *)d inXML:(NSMutableString *)ms forName:(NSString *)s nestingLevel:(int)nl;
{
// interne Methode
    NSArray *a1=[d allKeys];
    NSArray *a2=[d allObjects];
    NSString *dictName = [s stringWithXMLEscapes];
    NSString *nesting = [NSString stringWith:nl timesString:@"\t"];
    int i,j;

    [ms appendString:[NSSWF @"%@<%@>\n",nesting,dictName]];
    for(i=0,j=[a1 count];i<j;i++){
        NSString *k = [a1 oai:i];
        id o=[a2 oai:i];
        if([o isKindOfClass:[NSString class]]){
            NSString *s1 = [k stringWithXMLEscapes];
            [ms appendString:[NSSWF @"%@\t<%@>%@</%@>\n",nesting,s1,[o stringWithXMLEscapes],s1]];
        }else{
            if([o isKindOfClass:[NSDictionary class]]){
                [self encodeDictionary:o inXML:ms forName:k nestingLevel:nl + 1];
            }else if([o isKindOfClass:[NSArray class]]){
                [self encodeArray:o inXML:ms forName:k  nestingLevel:nl + 1];
            }
        }
    }
    [ms appendString:[NSSWF @"%@</%@>\n",nesting,dictName]];
}
- (void)encodeArray:(NSArray *)a inXML:(NSMutableString *)ms forName:(NSString *)s nestingLevel:(int)nl;
{
// interne Methode
    NSString *arrayName = [s stringWithXMLEscapes];
    NSString *nesting = [NSString stringWith:nl timesString:@"\t"];
    int i,j;

    [ms appendString:[NSSWF @"%@<%@>\n",nesting,arrayName]];
    for(i=0,j=[a count];i<j;i++){
        id o=[a oai:i];
        if([o isKindOfClass:[NSString class]]){
            [ms appendString:[NSSWF @"%@\t<NSArrayItem>%@</NSArrayItem>\n",nesting,[o stringWithXMLEscapes]]];
        }else{
            if([o isKindOfClass:[NSDictionary class]]){
                [self encodeDictionary:o inXML:ms forName:@"NSArrayItem" nestingLevel:nl + 1];
            }else if([o isKindOfClass:[NSArray class]]){
                [self encodeArray:o inXML:ms forName:@"NSArrayItem" nestingLevel:nl + 1];
            }
        }
    }
    [ms appendString:[NSSWF @"%@</%@>\n",nesting,arrayName]];
}
- (id)objectFromXML:(NSString *)xml;
{
// erst den Baum aufbauen; danach analysieren und entscheiden, was Dictionaries und Arrays sind;

    XMLObject *rootObject = nil,*currentObject=nil;
    NSString *xmlname;
    NSRange r;

    r = [xml rangeOfString:@"<?"];
    if(r.length){
        xml = [xml secureSubstringFromIndex:r.location + r.length];
        r = [xml rangeOfString:@"?>"];
        if(!(r.length)){
            LOGS(([NSSWF @"unmatched <? %@",[xml abbreviated60String]]));
            return nil;
        }else{
            xml = [xml secureSubstringFromIndex:r.location + r.length];
        }
    }
    while(YES){
// ist das naechste non-white-space Zeichen ein < kommt ein Objekt oder Kommentar oder endetag, sonst der Content des currentObject
        xml = [xml stringWithoutLeadingWhiteSpaceCRLF];
        if(!FILLED(xml))break; // fertig;
        if([xml characterAtIndex:0] == '<'){
            if([xml hasSecurePrefix:@"<!--"]){
// ein Kommentar
                xml = [xml substringFromIndex:4];
                r = [xml rangeOfString:@"-->"];
                if(!(r.length)){
                    LOGS(([NSSWF @"missing end of comment --> %@",[xml abbreviated60String]]));
                    return nil;
                }else{
                    xml = [xml secureSubstringFromIndex:r.location + r.length];
                }
            }else if([xml hasSecurePrefix:@"</"]){
// beendet currentObject;
// name im endetag muss mit name des currentObject uebereinstimmen
                xml = [xml substringFromIndex:2];
                r = [xml rangeOfString:@">"];
                if(!(r.length)){
                    LOGS(([NSSWF @"missing > in endtag %@",[xml abbreviated60String]]));
                    return nil;
                }else{
                    xmlname = [[xml secureSubstringToIndex:r.location]stringBySubstitutingXMLEscapes];

                    if(!FILLED(xmlname)){
                        LOGS(([NSSWF @"empty Name in endtag %@ ",[xml abbreviated60String]]));
                        return nil;
                    }
                    if(![xmlname iE:[currentObject name]]){
                        LOGS(([NSSWF @"Name %@ doesn't match name of starttag %@",[xml abbreviated60String],[currentObject name]]));
                        return nil;
                    }
                    xml = [xml secureSubstringFromIndex:r.location + r.length];
                    
// dieses Objekt ist fertig geparsed
                    currentObject = [currentObject parent];
                }
            }else{
// hier beginnt vermutlich ein Name
                xml = [xml substringFromIndex:1];
                r = [xml rangeOfString:@">"];
                if(!(r.length)){
                    LOGS(([NSSWF @"missing > %@",[xml abbreviated60String]]));
                    return nil;
                }else{
                    xmlname = [[xml secureSubstringToIndex:r.location]stringBySubstitutingXMLEscapes];

                    if(!FILLED(xmlname)){
                        LOGS(([NSSWF @"empty Name %@",[xml abbreviated60String]]));
                        return nil;
                    }
                    if(!rootObject){
                        rootObject = [XMLObject xmlObjectWithName:xmlname];
                        currentObject = rootObject;
                    }else{
                        XMLObject *o = [XMLObject xmlObjectWithName:xmlname];
                        [o setParent:currentObject];
                        [[currentObject children]addObject:o];
                        [currentObject setTyp:XMLObjectTyp_Container];
                        currentObject = o;
                    }
                    xml = [xml secureSubstringFromIndex:r.location + r.length];
                }
            }
        }else{
            [currentObject setTyp:XMLObjectTyp_Element];
// content des element
            r = [xml rangeOfString:@"</"];
            if(!(r.length)){
                LOGS(([NSSWF @"missing endtag of element </ %@",[xml abbreviated60String]]));
                return nil;
            }else{
                [currentObject setContent:[[xml secureSubstringToIndex:r.location]stringBySubstitutingXMLEscapes]];
                xml = [xml secureSubstringFromIndex:r.location + r.length];
// name im endetag muss mit name des currentObject uebereinstimmen
                r = [xml rangeOfString:@">"];
                if(!(r.length)){
                    LOGS(([NSSWF @"missing > in endtag %@",[xml abbreviated60String]]));
                    return nil;
                }else{
                    xmlname = [[xml secureSubstringToIndex:r.location]stringBySubstitutingXMLEscapes];

                    if(!FILLED(xmlname)){
                        LOGS(([NSSWF @"empty Name in endtag %@",[xml abbreviated60String]]));
                        return nil;
                    }
                    if(![xmlname iE:[currentObject name]]){
                        LOGS(([NSSWF @"Name %@ doesn't match name of starttag %@",[xml abbreviated60String],[currentObject name]]));
                        return nil;
                    }
                    xml = [xml substringFromIndex:r.location + r.length];
// dieses Objekt ist fertig geparsed
                    currentObject = [currentObject parent];
                }
            }
        }
    }
    if(!rootObject){
        LOGS(@"no rootObject in xml");
        return nil;
    }
    if([rootObject typ]==XMLObjectTyp_Element){
        LOGS(@"rootObject ist kein Container-Objekt");
        return nil;
    }

// jetzt den Baum analysieren
    if([rootObject determineConreteContainer]){
        if([rootObject ma])return [rootObject ma];
        if([rootObject md])return [rootObject md];
    }
    
    return nil;

}
- (NSData *)httpGet:(NSString *)uri host:(NSString *)host;
{
    NSMutableData *response = [NSMutableData dataWithCapacity:4096];
    PBSocket *socket;

    if(!FILLED(uri) || !FILLED(host)){LOGS(@"no host/uri"); return response;}
    if(!(socket = [PBSocket socket])){LOGS(@"no socket"); return response;}
    [socket connectToHostName:host port:80];
    if(![socket connected]){LOGS(@"no connect"); return response;}
    [socket writeString:[NSSWF @"GET %@ HTTP/1.0\r\nHost: %@\r\n\r\n",uri,host]];
    [socket readData:response];
    return response;
}
- (NSData *)httpPost:(NSString *)uri host:(NSString *)host soa:(NSString *)soa parm:(NSDictionary *)parm docType:(NSString *)docType;
{
// soa ist der optionale Name eines soa-Aufrufes; in Aprica ist dies ein Script-Name;
// parm ist ein Dictionary, das in XML umgewandelt zum host transportiert wird
// docType ist der optionale Name des XML-Dokuments; falls leer, wird ein default genommen;

    NSMutableData *response = [NSMutableData dataWithCapacity:4096];
    PBSocket *socket;
    NSMutableString *ms = [NSMutableString stringWithCapacity:4096]; // hier wird der request zusammengebaut

    if(!FILLED(uri) || !FILLED(host)){LOGS(@"no host/uri"); return response;}
    if(!(socket = [PBSocket socket])){LOGS(@"no socket"); return response;}
    [socket connectToHostName:host port:80];
    if(![socket connected]){LOGS(@"no connect"); return response;}
    [ms setString:[NSSWF @"POST %@ HTTP/1.0\r\n",uri]];
    [ms appendString:[NSSWF @"Host: %@\r\n",host]];
    if(FILLED(soa)){
        [ms appendString:[NSSWF @"soa: %@\r\n",soa]]; // ein zusaetzlicher Header nimmt das Kommando auf
    }
    if(parm){
        NSString *xml;

        if(!FILLED(docType))docType = @"pirmin";
        xml = [self encodeObject:parm withDocType:docType];

        [ms appendString:[NSSWF @"Content-Length: %i\r\n\r\n",[xml length]]];
        [ms appendString:xml];
    }else{
        [ms appendString:@"Content-Length: 0\r\n\r\n"];
    }
    if(log_changes)LOG(ms);
    [socket writeString:ms];
    [socket readData:response];
    return response;
}

@end
