MaCocoa 062

Capitolo 062 - Esportazione dati

Ancora un breve capitolo per esportare in un file di testo l'elenco dei file presenti all'interno del catalogo.

Sorgenti: nessuna

Prima stesura: 10 novembre 2004

I nomi delle preferenze

Prima di esportare dati, cambio i nomi delle preferenze. Fino ad ora mi ero trovato ad utilizzare delle #define per individuare delle stringhe con cui individuare alcune grandezze la cui specifica era a cura dell'utente. Ad esempio:

#define    keyColModDate    @"ColModDate"
...
#define    keyCDLCW_ModDate    @"CDLCW_ModDate"

specificano la prima la presenza o meno di una colonna nella finestra del catalogo, mentre la seconda specifica la dimensione in pixel nella visualizzazione di quella stessa colonna. Ogni volta che aggiunto preferenze riguardanti i singoli attributi di un file, devo aggiungere una diecina di #define, una per ogni attributo.

Trovo tutto ciò piuttosto scomodo, ed ho quindi deciso di lavorare in altro modo. Il punto di partenza è il nome di quell'attributo, che è anche l'identificatore della colonna. Sono un gruppo di #define in CatFileInfo.h:

#define        COLID_FILENAME        @"fileName"
#define        COLID_MODDATE        @"modDate"
...

Per ottenere un nome di preferenza, faccio precedere la stringa da un prefisso opportuno, costruendo così una nuova stringa, che uso appunto come identificatore della grandezza.

#define        keyDsplPrefPrefix        @"CDLDF_"
#define        keyExprPrefPrefix        @"CDLPF_"
#define        keyColWPrefPrefix        @"CDLCW_"

Ho definito quindi questi tre prefissi, per indicare il primo la visualizzazione o meno di una colonna, mentre il terzo specifica la larghezza di una colonna; il secondo prefisso sarà utilizzato poco sotto per determinare l'esportazione o meno di quell'attributo in un file di testo.

Ho poi definito una sintetica funzione in djZeroUtils.m per costruire i nomi:

NSString *
bldStrPref( NSString * pref, NSString * colid )
{
    return ( [ pref stringByAppendingString: colid] );
}

Infine, con infinita pazienza, sono andato a sostituire nell'intero codice tutte le occorrenze delle precedenti #define con nuove espressioni.

A titolo di esempio, mostro solo come cambia il metodo che salva le dimensioni delle colonne all'interno delle preferenze:

- (BOOL)
shouldCloseDocument
{
    NSTableColumn        * tc ;
    NSNumber            * cw ;
    
#define        SET_COLWIDTH_PREF( quale )                        \
    tc = [ fullList tableColumnWithIdentifier: quale ];        \
    if ( tc )                                                \
    {                                                        \
        cw = [ NSNumber numberWithInt: [ tc width ] ] ;        \
        [ UserPrefs setSinglePrefValue: cw forKey:            \
            bldStrPref(keyColWPrefPrefix, quale) ];        \
    }

    // ne approfitto per mettere a posto le larghezze di default
    tc = [ fullList tableColumnWithIdentifier: COLID_FILENAME];
    cw = [ NSNumber numberWithInt: [ tc width ] ] ;
    [ UserPrefs setSinglePrefValue: cw forKey: bldStrPref(keyColWPrefPrefix, COLID_FILENAME) ];
    SET_COLWIDTH_PREF( COLID_MODDATE ) ;
    ...
    SET_COLWIDTH_PREF( COLID_MODDATE ) ;
    return ( YES );
}

Attenzione che c'è anche un altro importante file da modificare: si tratta di defCodeValues.dict, dove sono contenuti i valori di default. Occorre cambiare tutti i nomi:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>CDLDF_creatDate</key>
    <true/>
    <key>CDLDF_creatorCode</key>
    <false/>
    ...
    <key>ExpandBundle</key>
    <false/>
    <key>ShowDotFiles</key>
    <true/>
</dict>
</plist>

Alla fine, sono rimaste due sole preferenze con un nome proprio, le grandezze booleane che indicano l'espansione o meno dei bundle e la visualizzazione o meno dei così detti dot file.

Esportare in un file di testo

Accade talvolta di dover trasferire i dati di un programma verso altri programmi; spesso il formato di interscambio non è altro che un semplice file di testo. Mi prefiggo quindi di produrre un file di testo con al suo interno l'elenco dei file presenti nel catalogo prescelto. Per complicarmi la vita, decido di utilizzare ancora una volta il campo is2Print per determinare se un dato file (e sui discendenti se si tratta di una cartella) è esportato oppure no; inoltre, permetto all'utente di specificare quali informazioni relative al file sono presenti. L'utente può quindi determinare, all'interno delle preferenze, quali campi esportare, così come decide quali campi sono visualizzati nella finestra.

figura 01

figura 01

Allo scopo, predispongo una finestra delle preferenze modificata: ho aggiunto una NSTabView con due viste. Nella prima vista sono comprese le preferenze di quali colonne visualizzare; nella seconda vista, assolutamente uguale, si specificano quali colonne/attributi esportare. Ho aggiunto un outlet verso la NSMatrix con i nuovi pulsanti, e ho modificato alcuni metodi di PrefWinCtl per tenere conto dei nuovi valori.

A titolo di esempio, riporto il metodo che aggiorna le preferenze secondo i desideri dell'utente:

- (void)
userSelectUpdate:(id)sender
{

#define        MATRIX_GET_STATE( key, row, col )                                            \
    [defDispValues setObject:                                                            \
        ([[colDspMatrix cellAtRow: row column:col] state ] ? lsPrefsYes : lsPrefsNo)     \
        forKey:key]

    // devo recuperare i valori dalla finestra
    [defDispValues setObject:
        ([expandBundleButton state] ? lsPrefsYes : lsPrefsNo)
        forKey:keyExpandBundle];
    [defDispValues setObject:
        ([showDotFilesButton state] ? lsPrefsYes : lsPrefsNo)
        forKey:keyShowDotFiles];
    // recupero i valori secondo i controlli ed assegno le prefs
    MATRIX_GET_STATE( bldStrPref(keyDsplPrefPrefix, COLID_MODDATE),     0, 0 );
    MATRIX_GET_STATE( bldStrPref(keyDsplPrefPrefix, COLID_CREATDATE),     0, 1 );
    MATRIX_GET_STATE( bldStrPref(keyDsplPrefPrefix, COLID_FILESIZE),     1, 0 );
    MATRIX_GET_STATE( bldStrPref(keyDsplPrefPrefix, COLID_OSTYPE),        1, 1 );
    MATRIX_GET_STATE( bldStrPref(keyDsplPrefPrefix, COLID_GROUPNAME),     2, 0 );
    MATRIX_GET_STATE( bldStrPref(keyDsplPrefPrefix, COLID_OSCREATOR),     2, 1 );
    MATRIX_GET_STATE( bldStrPref(keyDsplPrefPrefix, COLID_OWNERNAME),     3, 0 );
    MATRIX_GET_STATE( bldStrPref(keyDsplPrefPrefix, COLID_POSIXPERM),     3, 1 );

#define        MXEXPR_GET_STATE( key, row, col )                                            \
    [defDispValues setObject:                                                            \
        ([[colPrtMatrix cellAtRow: row column:col] state ] ? lsPrefsYes : lsPrefsNo)     \
        forKey:key]
        
    MXEXPR_GET_STATE( bldStrPref(keyExprPrefPrefix, COLID_MODDATE),     0, 0 );
    MXEXPR_GET_STATE( bldStrPref(keyExprPrefPrefix, COLID_CREATDATE),     0, 1 );
    MXEXPR_GET_STATE( bldStrPref(keyExprPrefPrefix, COLID_FILESIZE),     1, 0 );
    MXEXPR_GET_STATE( bldStrPref(keyExprPrefPrefix, COLID_OSTYPE),        1, 1 );
    MXEXPR_GET_STATE( bldStrPref(keyExprPrefPrefix, COLID_GROUPNAME),     2, 0 );
    MXEXPR_GET_STATE( bldStrPref(keyExprPrefPrefix, COLID_OSCREATOR),     2, 1 );
    MXEXPR_GET_STATE( bldStrPref(keyExprPrefPrefix, COLID_OWNERNAME),     3, 0 );
    MXEXPR_GET_STATE( bldStrPref(keyExprPrefPrefix, COLID_POSIXPERM),     3, 1 );
    // recuperati i valori; li assegno ai correnti
    // salvo le preference su file
    [ UserPrefs savePrefsToDefault: defDispValues ];
    // e per finire nascondo la finestra
    [[self window] setIsVisible: FALSE ];
}

Non ho fatto altro che applicare le modifica sul nome delle preferenze, ed aggiungere nuove preferenze, molto simili alle precedenti.

Esportazione

Sono a questo punto pronto per esportare i dati. Aggiungo al MainMenu.nib una voce di menu che scateni l'intero processo chiamando il metodo textExport della classe ListWinCtl:

- (void)
textExport: (id)sender
{
    NSSavePanel    *sPanel = [ NSSavePanel savePanel ] ;
    // personalizzo il pannello di salvataggio
    [ sPanel setTitle:@"Export text file" ];
    // i file salvati avranno l'estensione lscat
    [ sPanel setRequiredFileType: @"txt" ];
    if ( [ sPanel runModal ] == NSOKButton )
    {
        // recupero il nome del file
        NSString    * aFile = [ sPanel filename ] ;
        NSString    * exportStr ;
        exportStr = [ [[ self document] dataSource] exportData ];
        if ( ! [ exportStr writeToFile: aFile atomically: YES ] )
        {
            // che ne so
        }
    }
}

Questo metodo chiede la specifica di un file di testo tramite il consueto pannello; se l'utente non cambia idea e fornisce un adeguato percorso, il metodo exportData produce una NSString con tutti i dati da esportare, e la semplice istruzione writeToFile: produce il file testo voluto. La potenza di Cocoa e soprattutto della classe NSString permette di sorvolare su molti dettagli.

Adesso non rimane che scrivere il metodo exportData, proprio della classe CatDataSrc. Ancora una volta, si tratta di eseguire una discesa ricorsiva della base di dati e di produrre, per ogni elemento attraversato, una stringa apposita. Questa volta utilizzo un metodo piuttosto pedestre:

- (NSString *)
exportData
{
    NSString    * expStr = [ NSString string ] ;
    int            jj ;
    for ( jj = 0 ; jj < [ startPoint count ] ; jj ++ )
    {
        FileStruct *    item ;
        item = [ startPoint objectAtIndex: jj ] ;
        expStr = [ item exportData: expStr withLevel: 0 ] ;
    }
    return ( expStr ) ;
}

Qui si esaminano tutti i volumi presenti, e per ognuno di essi si comanda l'esportazione dei dati. Qui occorre notare come la stringa sia passata come parametro e poi restituita dal metodo; si suppone che alla stringa siano state aggiunte le informazioni proprie di quell'elemento (e di tutti i discendenti dell'elemento). Inoltre, come già visto altrove, faccio in modo che gli elementi siano indentati in accordo alla profondità della struttura delle cartelle.

Al termine di questa cascata di metodi di esportazione c'è finalmente questo metodo, proprio della classe FileStruct:

- (NSString *)
exportData: (NSString *) expStr withLevel: (int) lev
{
    if ( [ self is2Print ] )
    {
        int    i ;
        int    numFile = [ fileList count ] ;
        expStr = [ self writeExportToString: expStr withLevel: lev ] ;
        for ( i = 0; i < numFile; i++)
        {
            FileStruct    * tmpFile ;
            // costruisco l'albero
            tmpFile = [ fileList objectAtIndex: i ] ;
            expStr = [ tmpFile exportData: expStr withLevel: (lev+1) ] ;
        }
    }
    return ( expStr );
}

La prima cosa che il metodo si chiede è il valore di is2Print. Se Falso, restituisce intonsa la stringa passatagli, dal momento che non si devono aggiungere dati a quelli da esportare. Se invece is2Print è vero, aggiunge i propri dati alla stringa e poi passa il compito ordinatamente ai propri discendenti (se presenti). Alla fine, la stringa, ripetutamente aggiornata, è restituita al chiamante.

Formattare il testo esportato

Il lavoro finale, di produzione della stringa di testo da aggiungere alla stringa già disponibile, è svolta da un ulteriore metodo; questo non è nel suo impianto particolarmente difficile, ma ci sono alcuni piccoli accorgimenti da utilizzare se si intende ottenere un file testo leggibile in maniera decente.

- (NSString *)
writeExportToString: (NSString *) expStr withLevel: (int) lev
{
    NSString    * tmpStr ;
    // la stringa di prefisso con un po' di tabulazioni, ed il nome del file
    tmpStr = [ expStr stringByAppendingFormat: @"%@%@\t", prefixLevString( lev ), [ self fileName ] ];
    // esamino poi i vari attributi: data di modifica
    if ( [[ UserPrefs getPrefValue: bldStrPref(keyExprPrefPrefix, COLID_MODDATE)] boolValue] )
    {
        // costruisco un formattatore per le date
        NSDate        * tmp = [ self modDate ] ;
        NSDateFormatter *dateFormat = [[NSDateFormatter alloc]
            initWithDateFormat:@"%d %b %Y %H:%M" allowNaturalLanguage:NO];        
        NSString * localString = [ dateFormat stringForObjectValue: tmp ] ;
        tmpStr = [ tmpStr stringByAppendingFormat: @"%@\t", localString ];
    }
    // data di creazione
    if ( [[ UserPrefs getPrefValue: bldStrPref(keyExprPrefPrefix, COLID_CREATDATE)] boolValue] )
    {
        // costruisco un formattatore per le date
        NSDate        * tmp = [ self creatDate ] ;
        NSDateFormatter *dateFormat = [[NSDateFormatter alloc]
            initWithDateFormat:@"%d %b %Y %H:%M" allowNaturalLanguage:NO];        
        NSString * localString = [ dateFormat stringForObjectValue: tmp ] ;
        tmpStr = [ tmpStr stringByAppendingFormat: @"%@\t", localString ];
    }
    // dimensione del file
    if ( [[ UserPrefs getPrefValue: bldStrPref(keyExprPrefPrefix, COLID_FILESIZE)] boolValue] )
    {
        // costruisco un formattatore per la dimensioen del file
        FileSizeForm    * numFmt = [ [ FileSizeForm alloc ] init ] ;
        long long    tmp = [ self fileSize ] ;
        NSString * localString = [ numFmt stringForObjectValue: [ NSNumber numberWithLongLong: tmp ] ] ;
        tmpStr = [ tmpStr stringByAppendingFormat: @"%@\t", localString ];
    }
    // nome del gruppo e del proprietario
    if ( [[ UserPrefs getPrefValue: bldStrPref(keyExprPrefPrefix, COLID_GROUPNAME)] boolValue] )
    {
        // e' una stringa, aggiungo brutalmente
        tmpStr = [ tmpStr stringByAppendingFormat: @"%@\t", [ self ownGroupName ] ];
    }
    if ( [[ UserPrefs getPrefValue: bldStrPref(keyExprPrefPrefix, COLID_OWNERNAME)] boolValue] )
    {
        // e' una stringa, aggiungo brutalmente
        tmpStr = [ tmpStr stringByAppendingFormat: @"%@\t", [ self ownerName ] ];
    }
    // posix permissions
    if ( [[ UserPrefs getPrefValue: bldStrPref(keyExprPrefPrefix, COLID_POSIXPERM)] boolValue] )
    {
        // formattatore per i permessi di lettura/scrittura/esecuzione
        FilePosixPerm    * numFmt = [ [ FileSizeForm alloc ] init ] ;
        long    tmp = [ self filePosixPerm ] ;
        NSString * localString = [ numFmt stringForObjectValue: [ NSNumber numberWithLong: tmp ] ] ;
        tmpStr = [ tmpStr stringByAppendingFormat: @"%@\t", localString ];
    }
    // tipo e creatore
    if ( [[ UserPrefs getPrefValue: bldStrPref(keyExprPrefPrefix, COLID_OSCREATOR)] boolValue] )
    {
        // formattatore per tipo/creatore in OS9
        TOS9TCForm    * numFmt = [ [ FileSizeForm alloc ] init ] ;
        long    tmp = [ self creatorCode ] ;
        NSString * localString = [ numFmt stringForObjectValue: [ NSNumber numberWithLong: tmp ] ] ;
        tmpStr = [ tmpStr stringByAppendingFormat: @"%@\t", localString ];
    }
    if ( [[ UserPrefs getPrefValue: bldStrPref(keyExprPrefPrefix, COLID_OSTYPE)] boolValue] )
    {
        // formattatore per tipo/creatore in OS9
        TOS9TCForm    * numFmt = [ [ FileSizeForm alloc ] init ] ;
        long    tmp = [ self typeCode ] ;
        NSString * localString = [ numFmt stringForObjectValue: [ NSNumber numberWithLong: tmp ] ] ;
        tmpStr = [ tmpStr stringByAppendingFormat: @"%@\t", localString ];
    }
    return ( [ tmpStr stringByAppendingString: @"\n" ] ) ;
}

In effetti, finché l'attributo è ben rappresentato da una stringa, uso l'attributo così come si trova? Negli altri casi, utilizzo invece (come al solito) un formattatore per poterne dare una rappresentazione più leggibile.

Licenza Creative Commons
Eccetto dove diversamente specificato, i contenuti di questo sito sono rilasciati sotto Licenza Creative Commons.
Pagina a cura di Livio Sandel (macocoa2012@gmail.com).