Ejemplo de ByteArray: Lectura de un archivo .zip

Adobe AIR 1.0 y posterior

Este ejemplo muestra cómo leer un archivo .zip sencillo que contiene varios archivos de diversos tipos. Para realizarlo, se extraen los datos pertinentes de los metadatos para cada archivo, se descomprime cada archivo para ponerlo en un ByteArray y se escribe el archivo en el escritorio.

La estructura general de un archivo .zip se basa en la especificación de PKWARE Inc., que se mantiene en http://www.pkware.com/documents/casestudies/APPNOTE.TXT. Primero hay una cabecera de archivo y datos de archivo para el primer archivo individual del archivo .zip, seguido de un par de cabecera-datos de archivo para cada archivo adicional. (La estructura de la cabecera de archivo se describe más adelante). A continuación, el archivo .zip puede incluir como opción un registro de descriptor de datos (generalmente al crear el archivo zip en la memoria, más que al guardarlo en un disco). A continuación vienen varios elementos opcionales más: cabecera de descifrado del archivo comprimido, registro de datos adicionales del archivo comprimido, estructura del directorio central, registro de fin del directorio central Zip64, localizador de fin del directorio central Zip64, y registro de fin del directorio central.

El código en este ejemplo se escribe para que solo se analicen los archivos zip que no contengan carpetas y no espera que haya registros de descripción de datos. Pasa por alto toda la información que haya después del último dato del archivo.

El formato de la cabecera de archivo para cada archivo es el siguiente:

firma de cabecera de archivo

4 bytes

versión necesaria

2 bytes

indicador de bits universal

2 bytes

método de compresión

2 bytes (8=DEFLATE; 0=UNCOMPRESSED)

hora de última modificación del archivo

2 bytes

fecha de última modificación del archivo

2 bytes

crc-32

4 bytes

tamaño comprimido

4 bytes

tamaño descomprimido

4 bytes

longitud de nombre de archivo

2 bytes

longitud de campo adicional

2 bytes

nombre de archivo

variable

campo adicional

variable

Después de la cabecera del archivo vienen los datos del archivo, que pueden estar comprimidos o sin comprimir, según el indicador de método de compresión. El indicador será 0 (cero) si los datos están sin comprimir, u 8 si los datos están comprimidos con el algoritmo DEFLATE, u otro valor para otros algoritmos de compresión.

La interfaz de usuario para este ejemplo consta de una etiqueta y un área de texto (taFiles). La aplicación escribe la información siguiente en el área de texto para cada archivo que encuentra en el archivo .zip: el nombre del archivo, el tamaño comprimido y el tamaño sin comprimir. El siguiente documento MXML define la interfaz de usuario para la versión Flex de la aplicación:

<?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>

El principio del programa realiza las siguientes tareas:

  • Importa las clases que se requieran.

    import flash.filesystem.*; 
    import flash.utils.ByteArray; 
    import flash.events.Event;
  • Define la interfaz de usuario para 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);
  • Define el ByteArray bytes.

    var bytes:ByteArray = new ByteArray(); 
  • Define variables para guardar los metadatos de la cabecera del archivo.

    // 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; 
  • Define los objetos File (zfile) y FileStream (zStream) para representar el archivo .zip, y especifica la ubicación del archivo .zip del que se extrajeron los archivos (un archivo llamado “HelloAIR.zip” en el directorio del escritorio).

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

En Flex, el código del programa empieza en el método init(), al que se llama como controlador de creationComplete para la etiqueta raíz mx:WindowedApplication.

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

El programa empieza por abrir el archivo .zip en modo READ (lectura).

    zStream.open(zfile, FileMode.READ); 

A continuación define la propiedad endian de bytes en LITTLE_ENDIAN para indicar que el orden de bytes de los campos numéricos es con el byte menos significativo primero.

    bytes.endian = Endian.LITTLE_ENDIAN; 

Seguidamente, la sentencia while() inicia un bucle que continúa hasta que la posición actual en la secuencia de archivos sea superior o igual al tamaño del archivo.

    while (zStream.position < zfile.size) 
    {

La primera sentencia del bucle lee los 30 primeros bytes de la secuencia de archivos para ponerlo en el ByteArray bytes. Los 30 primeros bytes conforman la parte de tamaño fijo de la cabecera del primer archivo.

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

A continuación el código lee un entero (signature) en los primeros bytes de la cabecera de 30 bytes. La definición del formato ZIP estipula que la firma de cada cabecera de archivo es el valor hexadecimal 0x04034b50; si la firma es distinta, significa que el código se refiere a la parte del archivo .zip que es ajena a los archivos y no hay más archivos que extraer. En ese caso el código sale inmediatamente del bucle while en lugar de esperar hasta alcanzar el final del conjunto de bytes.

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

La siguiente parte del bucle lee el byte de la cabecera en la posición de desplazamiento 8 y guarda el valor en la variable compMethod. Este byte contiene un valor que indica el método de compresión que se utilizó para comprimir este archivo. Se admiten varios métodos de compresión, pero en la práctica para casi todos los archivos .zip se utiliza el algoritmo de compresión DEFLATE. Si el archivo actual está comprimido con compresión DEFLATE, compMethod es 8; si el archivo está sin comprimir, compMethod es 0.

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

A los 30 primeros bytes sigue una parte de longitud variable de la cabecera que contiene el nombre del archivo y, tal vez, un campo adicional. El tamaño de esta parte se guarda en la variable offset. El tamaño se calcula sumando la longitud del nombre del archivo y la longitud del campo adicional, leídas en la cabecera en las posiciones de desplazamiento 26 y 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

A continuación el programa lee la parte de longitud variable de la cabecera de archivo donde se indica la cantidad de bytes guardados en la variable offset.

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

El programa lee el nombre del archivo en la parte de longitud variable de la cabecera y lo muestra en el área de texto junto con los tamaños del archivo comprimido (en el archivo zip) y sin comprimir (original).

// 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'; 

En el ejemplo se lee el resto del archivo de la secuencia de archivos para ponerlo en bytes durante la longitud especificada por el tamaño comprimido, sobrescribiendo la cabecera del archivo en los primeros 30 bytes. El tamaño comprimido es exacto aunque el archivo esté sin comprimir porque en ese caso el tamaño comprimido es igual al tamaño del archivo sin comprimir.

    // 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);

A continuación el código en el ejemplo descomprime el archivo comprimido y llama a la función outfile() para escribirlo en la secuencia de archivos de salida. Pasa a outfile() el nombre del archivo y el conjunto de bytes que contiene los datos del archivo.

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

En el ejemplo mencionado anteriormente, bytes.uncompress(CompressionAlgorithm.DEFLATE) solo funcionará en las aplicaciones de AIR. Para no compimir los datos desinflados para AIR y Flash Player, invoque la función inflate() de ByteArray.

Las llaves finales indican el final del bucle while y del método init() y el código de la aplicación de Flex, excepto para el método outFile(). La ejecución regresa al principio del bucle while y sigue procesando los siguientes bytes del archivo .zip, sea extrayendo otro archivo o finalizando el procesamiento del archivo .zip si es que se ha procesado el último archivo.

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

La función outfile() abre un archivo de salida en modo WRITE (lectura) en el escritorio y le da el nombre suministrado por el parámetro filename. A continuación escribe los datos de los archivos del parámetro data en la secuencia de archivos de salida (outStream) y cierra el archivo.

// 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(); 
}