HotXLS abre libros escritos por Excel 2.0, 3.0 y 4.0 directamente desde Delphi y C++Builder. Estos archivos son anteriores al contenedor de documento compuesto OLE que usa todo .xls posterior, así que son flujos de registros BIFF sin procesar y sin envoltorio de almacenamiento alguno, y un lector construido para BIFF8 no encontrará ni una sola estructura reconocible dentro de ellos. Abrir uno usa la misma llamada Open que cualquier otro libro; el lector detecta el formato y cambia de vía
Estos archivos siguen apareciendo, que es la única razón por la que todo esto importa. Los archivos de ingeniería, la retención de registros gubernamentales, los datos de laboratorio de instrumentos cuyo software de control se escribió en 1993, y los sistemas contables de larga duración dejaron todos atrás libros BIFF2 y BIFF4. El Excel moderno se niega directamente a abrir varios de ellos, tras haber eliminado los conversores heredados por motivos de seguridad, lo que deja un conjunto de datos que nadie puede leer con una herramienta que alguien tenga
¿Qué diferencia a un libro anterior a OLE?
Todo .xls desde Excel 5.0 en adelante es un archivo compuesto OLE2, un pequeño sistema de archivos dentro de un archivo, con el libro residiendo en un flujo llamado Workbook o Book. Analizar uno empieza por analizar ese contenedor, como se describe en el formato binario de archivo compuesto en Pascal
BIFF2 a BIFF4 no tienen contenedor. El archivo empieza directamente con un registro BOF, y el número de registro de ese BOF codifica la generación: $0009 para BIFF2, $0209 para BIFF3 y $0409 para BIFF4. HotXLS valida la longitud del cuerpo del BOF, que está entre cuatro y seis bytes, y el tipo de subflujo, $0010 para una hoja de cálculo, $0020 para un gráfico y $0040 para una hoja de macros, antes de comprometerse con la vía sin procesar. Esa validación es lo que evita que un archivo corrupto o mal identificado se interprete como un libro muy antiguo
Tres generaciones, tres disposiciones de registro
Los registros de celda son donde las generaciones divergen de forma más visible. BIFF2 ocupa un bloque contiguo de números de registro bajos, de $0001 a $0005 para celdas en blanco, enteras, numéricas, de etiqueta y booleanas o de error, y cada cuerpo lleva un campo de atributo de tres bytes donde las versiones posteriores colocan un índice de formato extendido. BIFF3 y BIFF4 abandonan eso y reutilizan los números de registro y las disposiciones de BIFF5, $0201, $0203, $0204 y $0205, con un índice XF de dos bytes
Ese último detalle provoca un fallo específico y fácil de diagnosticar mal. Un registro LABEL de BIFF3 o BIFF4 es estructuralmente idéntico a su equivalente de BIFF5, fila y columna seguidas del índice de formato y luego el recuento de caracteres. Escriba un lector que asuma la disposición de BIFF2 y leerá dos bytes de menos, luego se saldrá del final del registro y malinterpretará todo lo que viene después. El síntoma no es una excepción; es un libro que se lee con basura plausible dentro
Los registros de fórmula ocupan una numeración paralela en las tres, $0006, $0206 y $0406. Cuando una fórmula produce un resultado de cadena, esa cadena llega en un registro separado siguiente, $0007 o $0207, y la forma BIFF2 de ese registro usa un prefijo de longitud de un solo byte en lugar del de dos bytes usado más tarde
Por qué las fórmulas vuelven como valores, no como texto
HotXLS lee el resultado en caché de una fórmula en estos archivos y no intenta reconstruir la expresión de la fórmula. Este es un límite deliberado, no un hueco a la espera de rellenarse
La expresión analizada en BIFF2 a BIFF4 usa una codificación de tokens que difiere de la de BIFF5 y posteriores de formas que van más allá de lo cosmético: las longitudes de los tokens llevan prefijos distintos, los tokens de referencia tienen tamaños distintos, y las tablas de índice de funciones se renumeraron entre generaciones. Pasar esos bytes por un traductor de expresiones de BIFF8 no produce una fórmula incorrecta, produce una aleatoria. Leer el valor en caché proporciona el número o la cadena que Excel calculó por última vez, que es lo que una migración de archivo realmente necesita
El valor en caché reside en un desplazamiento dentro del registro que depende de la generación: byte 7 para BIFF2 y byte 6 para BIFF3 y BIFF4. Los valores especiales, cadenas, booleanos, errores y celdas en blanco, se codifican en una palabra marcadora de $FFFF con un discriminador, la misma convención que conservaron las generaciones de BIFF posteriores
Abrir uno
El código que llama no tiene nada de particular, que es precisamente la idea. La detección ocurre dentro de Open:
uses
lxHandle;
var
Book: TXLSWorkbook;
Sheet: TXLSWorksheet;
R, C: Integer;
V: Variant;
begin
Book := TXLSWorkbook.Create;
try
if Book.Open('archive\1993-inventory.xls') <> 1 then
begin
Writeln('unreadable - quarantine for manual review');
Exit;
end;
Sheet := Book.Sheets[1]; // Sheets[] está basado en 1
for R := Sheet.UsedRange.FirstRow + 1 to Sheet.UsedRange.LastRow + 1 do
for C := Sheet.UsedRange.FirstCol + 1 to Sheet.UsedRange.LastCol + 1 do
begin
V := Sheet.Cells[R, C].Value;
if not VarIsEmpty(V) then
Writeln(Format('R%dC%d = %s', [R, C, VarToStr(V)]));
end;
finally
Book.Free;
end;
end;
Observe la aritmética de índices de ese bucle. Los límites de UsedRange están basados en cero mientras que tanto la colección de hojas como el acceso a celdas están basados en uno, una inconsistencia anterior a la API actual y que se conserva por compatibilidad. Olvidar el ajuste audita el rectángulo equivocado y no informa de nada inusual mientras lo hace. Las comprobaciones previas económicas que evitan cargar un archivo por completo se tratan en la inspección ligera de libros
Qué no se obtiene, y qué hacer al respecto
El formato no se interpreta. HotXLS no analiza los registros XF y FONT de estas generaciones, así que las fuentes, los colores, los bordes y los formatos numéricos no están disponibles, y las celdas que Excel mostraba antes como fechas vuelven como sus números de serie sin procesar
Ese último caso hay que gestionarlo en el propio código en lugar de en el lector, y la razón es honesta: los formatos numéricos en BIFF2 a BIFF4 no son lo bastante fiables como para impulsar una decisión automática de fecha. Una columna de números de cinco dígitos podría ser fechas, o podría ser números de referencia. Convierta de forma deliberada, usando el sistema de fechas del libro, cuyas reglas se describen en los números de serie de fecha, el sistema de 1904 y los formatos numéricos:
// Decida por columna, nunca por valor: un número de cinco dígitos puede ser
// una fecha o un número de referencia, y el formato heredado no lo indicará
if ColumnHoldsDates(C) then
begin
// Los dos sistemas de fechas están separados por 1462 días, así que el mismo
// número de serie denota dos fechas separadas por cuatro años. Lea el sistema
// del libro en lugar de asumir uno
if Book.Date1904 then
Writeln(DateToStr(SerialToDate1904(V)))
else
Writeln(DateToStr(SerialToDate1900(V)));
end
else
Writeln(VarToStr(V));
Dos notas estructurales completan el panorama. Los registros de protección con contraseña y de página de códigos aparecen dentro del único flujo de hoja de cálculo en lugar de en un flujo a nivel de libro, porque no hay ningún flujo a nivel de libro en el que colocarlos, así que hay que reconocerlos en contexto de hoja de cálculo. Y un archivo BIFF2 a BIFF4 contiene exactamente un subflujo de hoja; los libros multihoja no existieron hasta que el formato obtuvo su contenedor
La vía de migración pragmática es, por tanto, de dos pasos: leer el archivo heredado para obtener sus valores, y luego escribir un libro moderno que lleve esos valores con el formato que se aplique uno mismo. La lectura heredada, la escritura moderna y todo lo intermedio se ejecutan en una sola biblioteca para Delphi y C++Builder, descrita en la página del componente de hojas de cálculo para Delphi de HotXLS