Esempio di ByteArray: lettura di un file zip

Adobe AIR 1.0 e versioni successive

In questo esempio viene illustrato come leggere un file zip semplice contenente vari file di tipi diversi. A tale scopo, è sufficiente estrarre dati pertinenti dai metadati per ciascun file, decomprimere ciascun file in un ByteArray e scrivere il file sul desktop.

La struttura generale di un file zip si basa sulle specifiche fornite da PKWARE Inc., riportate in http://www.pkware.com/documents/casestudies/APPNOTE.TXT . In primo luogo sono presenti un'intestazione file e dati file per il primo file nell'archivio zip, seguiti da una coppia 'intestazione file e dati file per ciascun file aggiuntivo (la struttura dell'intestazione file verrà descritta in seguito). In secondo luogo, il file zip include facoltativamente un record di descrizione dei dati (di solito quando il file zip di output viene creato in memoria anziché salvato sul disco). Sono quindi presenti diversi elementi opzionali aggiuntivi: intestazione di decrittazione dell'archivio, record dei dati extra dell'archivio, struttura di directory centrale, record di fine della directory centrale in formato Zip64, localizzatore di fine della directory centrale in formato Zip64 e record di fine della directory centrale.

Il codice in questo esempio è scritto solo per analizzare i file zip che non contengono cartelle, non prevede record di descrizione dei dati e ignora ogni tipo di informazione che segue gli ultimi dati dei file.

Il formato dell'intestazione per ciascun file è il seguente:

firma dell'intestazione del file

4 byte

versione richiesta

2 byte

flag di bit di scopo generale

2 byte

metodo di compressione

2 byte (8=DEFLATE; 0=UNCOMPRESSED)

ora dell'ultima modifica del file

2 byte

data dell'ultima modifica del file

2 byte

crc-32

4 byte

dimensione compressa

4 byte

dimensione non compressa

4 byte

lunghezza del nome file

2 byte

lunghezza del campo extra

2 byte

nome file

variabile

campo extra

variabile

L'intestazione del file è seguita dai dati del file veri e propri, che possono essere compressi o non compressi, a seconda del flag del metodo di compressione. Il valore de flag è 0 (zero) se i dati del file non sono compressi, 8 se sono compressi mediante l'algoritmo DEFLATE oppure hanno un valore diverso se è stato usato un altro algoritmo di compressione.

L'interfaccia utente per questo esempio è costituita da un'etichetta e un'area di testo ( taFiles ). L'applicazione scrive le seguenti informazioni nell'area di testo per ciascun file incontrato nel file zip: il nome del file, la dimensione compressa e quella non compressa. Il seguente documento MXML definisce l'interfaccia utente per la versione Flex dell'applicazione:

<?xml version="1.0" encoding="utf-8"?> 
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" creationComplete="init();"> 
    <mx:Script> 
        <![CDATA[ 
            // The application code goes here 
        ]]> 
    </mx:Script> 
    <mx:Form> 
        <mx:FormItem label="Output"> 
            <mx:TextArea id="taFiles" width="320" height="150"/> 
        </mx:FormItem> 
    </mx:Form> 
</mx:WindowedApplication>

L'inizio del programma effettua le seguenti operazioni:

  • Importa le classi richieste

    import flash.filesystem.*; 
    import flash.utils.ByteArray; 
    import flash.events.Event;
  • Definisce l'interfaccia utente per Flash

    import fl.controls.*; 
     
    //requires TextArea and Label components in the Library 
    var taFiles = new TextArea(); 
    var output = new Label(); 
    taFiles.setSize(320, 150); 
    taFiles.move(10, 30); 
    output.move(10, 10); 
    output.width = 150; 
    output.text = "Contents of HelloAir.zip"; 
    addChild(taFiles); 
    addChild(output);
  • Definisce il ByteArray bytes

    var bytes:ByteArray = new ByteArray(); 
  • Definisce le variabili per archiviare i metadati dall'intestazione del file

    // variables for reading fixed portion of file header 
    var fileName:String = new String(); 
    var flNameLength:uint; 
    var xfldLength:uint; 
    var offset:uint; 
    var compSize:uint; 
    var uncompSize:uint; 
    var compMethod:int; 
    var signature:int; 
  • Definisce gli oggetti File ( zfile ) e FileStream ( zStream ) per rappresentare il file zip, quindi specifica la posizione del file zip da cui i file vengono estratti: un file denominato “HelloAIR.zip” nella directory del desktop.

    // File variables for accessing .zip file 
    var zfile:File = File.desktopDirectory.resolvePath("HelloAIR.zip"); 
    var zStream:FileStream = new FileStream();

In Flex, il codice del programma viene avviato nel metodo init() , che viene chiamato come gestore creationComplete per il tag mx:WindowedApplication dell'elemento principale.

// for Flex 
private function init():void 
{

Il programma viene avviato aprendo il file zip in modalità di lettura (READ).

    zStream.open(zfile, FileMode.READ); 

Imposta quindi la proprietà endian di bytes su LITTLE_ENDIAN , a indicare che nell'ordine dei byte dei campi numerici il byte meno significativo è il primo.

    bytes.endian = Endian.LITTLE_ENDIAN; 

Successivamente, un'istruzione while() avvia un ciclo che continua fino a che la posizione corrente nel flusso di file è maggiore o uguale alla dimensione del file.

    while (zStream.position < zfile.size) 
    {

La prima istruzione all'interno del ciclo legge i primi 30 byte del flusso di file nel valore bytes di ByteArray. I primi 30 byte costituiscono la parte di dimensione fissa dell'intestazione del primo file.

        // read fixed metadata portion of local file header 
        zStream.readBytes(bytes, 0, 30);

In seguito, il codice legge un valore intero ( signature ) dai primi byte dell'intestazione di 30 byte. La definizione del formato ZIP consente di specificare che la firma per ogni intestazione file è costituita dal valore esadecimale 0x04034b50 ; se la firma è diversa, ciò significa che il codice è stato spostato al di là della parte file del file zip e che non ci sono altri file da estrarre. In quel caso il codice esce dal ciclo while anziché attendere la fine dell'array di byte.

        bytes.position = 0; 
        signature = bytes.readInt(); 
        // if no longer reading data files, quit 
        if (signature != 0x04034b50) 
        { 
            break; 
        }

La parte seguente del codice legge il byte di intestazione alla posizione di offset 8 e memorizza il valore nella variabile compMethod . Questo byte contiene un valore che indica il metodo di compressione utilizzato per il file. Sono consentiti diversi metodi di compressione, tuttavia in pratica quasi tutti i file zip utilizzano l'algoritmo di compressione DEFLATE. Se il file corrente è compresso mediante l'algoritmo DEFLATE, il valore di compMethod è 8; se il file non è compresso, compMethod sarà 0.

        bytes.position = 8; 
        compMethod = bytes.readByte();  // store compression method (8 == Deflate)

Dopo i primi 30 byte è presente una parte dell'intestazione di lunghezza variabile che contiene il nome del file ed eventualmente un campo aggiuntivo. Nella variabile offset è memorizzata la dimensione di questa parte. La dimensione viene calcolata aggiungendo la lunghezza del nome file e del campo extra, letta dall'intestazione agli offset 26 e 28.

        offset = 0;    // stores length of variable portion of metadata  
        bytes.position = 26;  // offset to file name length 
        flNameLength = bytes.readShort();    // store file name 
        offset += flNameLength;     // add length of file name 
        bytes.position = 28;    // offset to extra field length 
        xfldLength = bytes.readShort(); 
        offset += xfldLength;    // add length of extra field

In seguito, il programma legge la parte di lunghezza variabile dell'intestazione del file per il numero di byte archiviati nella variabile offset .

        // read variable length bytes between fixed-length header and compressed file data 
        zStream.readBytes(bytes, 30, offset);

Il programma legge il nome del file dalla parte a lunghezza variabile dell'intestazione e lo visualizza nell'area di testo insieme alle dimensioni del file compresso e non compresso (originale).

// Flash version 
        bytes.position = 30;  
        fileName = bytes.readUTFBytes(flNameLength); // read file name 
        taFiles.appendText(fileName + "\n"); // write file name to text area 
        bytes.position = 18; 
        compSize = bytes.readUnsignedInt();  // store size of compressed portion 
        taFiles.appendText("\tCompressed size is: " + compSize + '\n'); 
        bytes.position = 22;  // offset to uncompressed size 
        uncompSize = bytes.readUnsignedInt();  // store uncompressed size 
        taFiles.appendText("\tUncompressed size is: " + uncompSize + '\n'); 
        
// Flex version 
        bytes.position = 30;  
        fileName = bytes.readUTFBytes(flNameLength); // read file name 
        taFiles.text += fileName + "\n"; // write file name to text area 
        bytes.position = 18; 
        compSize = bytes.readUnsignedInt();  // store size of compressed portion 
        taFiles.text += "\tCompressed size is: " + compSize + '\n'; 
        bytes.position = 22;  // offset to uncompressed size 
        uncompSize = bytes.readUnsignedInt();  // store uncompressed size 
        taFiles.text += "\tUncompressed size is: " + uncompSize + '\n'; 

L'esempio legge il resto del file dal flusso del file in bytes per la lunghezza specificata dalla dimensione compressa, sovrascrivendo l'intestazione del file nei primi 30 byte. La dimensione compressa è accurata anche se il file non è compresso, in quanto in quel caso la dimensione compressa del file è uguale a quella non compressa.

    // read compressed file to offset 0 of bytes; for uncompressed files 
    // the compressed and uncompressed size is the same 
    if (compSize == 0) continue; 
    zStream.readBytes(bytes, 0, compSize);

Successivamente, nell'esempio, il file compresso viene decompresso e viene chiamata la funzione outfile() per scriverlo nel flusso di file di output. Vengono quindi trasmessi a outfile() il nome del file e l'array di byte contenente i dati del file.

        if (compMethod == 8) // if file is compressed, uncompress 
        { 
            bytes.uncompress(CompressionAlgorithm.DEFLATE); 
        } 
        outFile(fileName, bytes);   // call outFile() to write out the file

Nell'esempio citato in precedenza, bytes.uncompress(CompressionAlgorithm.DEFLATE) funziona solo nelle applicazioni AIR. Per decomprimere dati compressi in AIR e Flash Player, invocare la funzione inflate() di ByteArray.

La parentesi graffa di chiusura indica la fine del ciclo while , del metodo init() e del codice dell'applicazione, ad eccezione del metodo outFile() . L'esecuzione torna all'inizio del ciclo while e continua l'elaborazione dei byte seguenti nel file zip, mediante l'estrazione di un altro file oppure terminando l'elaborazione del file zip se l'ultimo file è stato elaborato.

    } // end of while loop 
} // for Flex version, end of init() method and application

La funzione outfile() apre un file di output nella modalità WRITE sul desktop, attribuendo ad esso il nome fornito dal parametro filename . I dati del file vengono quindi scritti dal parametro data al flusso di file di output ( outStream ) e il file viene chiuso.

// Flash version 
function outFile(fileName:String, data:ByteArray):void 
{ 
    var outFile:File = File.desktopDirectory; // destination folder is desktop 
    outFile = outFile.resolvePath(fileName);  // name of file to write 
    var outStream:FileStream = new FileStream(); 
    // open output file stream in WRITE mode 
    outStream.open(outFile, FileMode.WRITE); 
    // write out the file 
    outStream.writeBytes(data, 0, data.length); 
    // close it 
    outStream.close(); 
} 
private function outFile(fileName:String, data:ByteArray):void 
{ 
    var outFile:File = File.desktopDirectory; // dest folder is desktop 
    outFile = outFile.resolvePath(fileName);  // name of file to write 
    var outStream:FileStream = new FileStream(); 
    // open output file stream in WRITE mode 
    outStream.open(outFile, FileMode.WRITE); 
    // write out the file 
    outStream.writeBytes(data, 0, data.length); 
    // close it 
    outStream.close(); 
}