Analizando un archivo XLSX básico

Figura
Figura. Hoja de cálculo Excel web

Una de las ventajas del estándar abierto Office Open XML (OOX) es que se implementan aplicaciones para editar esos documentos en páginas web de uso abierto. Para probar esta implementación sobre exportar desde WXTABLE a XLSX ejecutaré los ejemplos con el Microsoft Excel en la web, como se observa en la Figura. Es una aplicación web a la que se tiene acceso desde OneDrive gratuitamente con alguna una cuenta del grupo Microsoft (HOTMAIL, YAHOO, OUTLOOK). También usaré la aplicación web GoogleSheets de uso abierto y gratuito con una cuenta GMAIL. Ambas aplicaciones soportan el estándar, aunque hay algunas cosas que las diferencian.

En cuanto a las aplicaciones EXCEL de escritorio, solo probaré los ejemplos en EXCEL 2010, versión 14. No puedo probarlo en otras versiones de escritorio pues no tengo la posibilidad de instalarlas o acceder a ellas. En este tema y siguientes sólo comentaré posibles diferencias en EXCEL 2010 cuando aparezcan.

El siguiente objeto JSON básico servirá como primer ejemplo de origen de datos para la función build({sheets, images, config}) del módulo xlsx.js, función que nos devolverá un objeto built con los archivos XML del XLSX. Usaremos la herramienta Exportador XLSX, que nos permitirá trazar el proceso de exportación. La Figura es una captura del resultado en Excel, cuyo XLSX puede descargar en este enlace spreadsheet-basic.xlsx.

{
    "sheets": {
        "Tab1": {
            "attributes": {},
            "styles": {},
            "values": [
                ["1","2"],
                ["a","b"]
            ]
        }
    },
    "images": []
}

El objeto built obtenido es el siguiente, donde hemos abreviado con puntos suspensivos el contenido de los elementos para ofrecer una visión global:

built = {
"[Content_Types].xml": `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <Types ...>...`,
"_rels/.rels": `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <Relationships ...>...`,
"xl/_rels/workbook.xml.rels": `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <Relationships ...">...`,
"xl/sharedStrings.xml": `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <sst ...>...`,
"xl/styles.xml": `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <styleSheet ...>...`,
"xl/workbook.xml": `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <workbook ...>...`,
"xl/worksheets/sheet1.xml": `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <worksheet ...>...`,
"xl/theme/theme1.xml": `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <a:theme ...>...`,
}

Las claves de ese objeto son las rutas de los archivos XML que se empaquetarán en un ZIP. Esta tabla resume la finalidad de cada archivo y su elemento raíz:

FinalidadNombre archivoElemento raíz
Tipos[Content_Types].xmlTypes
Relaciones_rels/.relsRelationships
Relacionesxl/_rels/workbook.xml.relsRelationships
Cadenas compartidasxl/sharedStrings.xmlsst
Estilosxl/styles.xmlstyleSheet
Libroxl/workbook.xmlworkbook
Hojaxl/worksheets/sheet1.xmlworksheet
Temasxl/theme/theme1.xmla:theme

A excepción de xl/worksheets/sheet1.xml que contiene los datos de la primera hoja (y única en este ejemplo), el resto de archivos comparten información para todo el libro. En los siguientes apartados haremos un breve repaso de cada archivo.

Figura
Figura. Un XLSX es un ZIP

Ya hemos dicho que un archivo XLSX se empaqueta en un ZIP. Si en una carpeta de Windows le cambiamos la extensión al archivo del ejemplo que hemos generado spreadsheet-basic.xlsx a spreadsheet-basic.zip podemos observar su contenido, tal como se observa en la Figura.

Esto sirve de gran ayuda porque podemos visualizar directamente el contenido de los archivos XML con algún visor XML.

En este tema haremos una descripción general de los archivos XML anteriores, con objeto de obtener una visión global de las partes que integran un XLSX.

Pero parar explicar lo que he hecho para exportar desde WXTABLE a XLSX necesitaré todos estos temas:

Figura
Figura. Muestras de tablas WXTABLE

Para explicar como traducir a XLSX necesitamos conocer como son los datos del origen a traducir. Estos temas se centran en traducir WXTABLE a XLSX. Para ello puede consultar la información del Gestor Tablas WXTABLE. El Gestor de Tablas usa el módulo calc.js (CALC), un motor de cálculo de expresiones que también se encarga de traducir expresiones a fórmulas XLSX. En la página Calculadora (módulo CALC) se muestra una aplicación donde puede consultar todas las constantes, operadores y funciones de ese módulo y que WXTABLE usa.

En la Figura puede ver la lista de ejemplos de muestra que puede abrir en la herramienta Exportador XLSX, ejemplos que usaremos en este tema y siguientes. El ejemplo Basic es el que hemos visto antes.

Tipos de archivo y relaciones XLSX

Figura
Figura. Archivos XML de una exportación a XLSX

En la herramienta para trazar la exportación podemos visualizar todos los archivos XML que se generan al exportar a XLSX. En la Figura se observa la lista de archivos del ejemplo Basic que vamos a analizar ahora.

El archivo [Content_Types].xml contiene los tipos de archivo. Su elemento raíz es <Types>. Establece los tipos de archivo que nos podemos encontrar en el conjunto de archivos empaquetados en el ZIP.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!-- Generated with xlsx.js from https://www.wextensible.com, version 21-07-2021 -->
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
    <Default ContentType="application/xml" Extension="xml"/>
    <Default ContentType="application/vnd.openxmlformats-package.relationships+xml" Extension="rels"/>
    <Override ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml" PartName="/xl/sharedStrings.xml"/>
    <Override ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml" PartName="/xl/styles.xml"/>
    <Override ContentType="application/vnd.openxmlformats-officedocument.theme+xml" PartName="/xl/theme/theme1.xml"/>
    <Override ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml" PartName="/xl/workbook.xml"/>
    <Override ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml" PartName="/xl/worksheets/sheet1.xml"/>
</Types>

Será siempre el mismo a excepción de cuando se agreguen nuevas hojas de cálculo. El ejemplo anterior tiene una única hoja de cálculo, cuyos datos se guardan en xl/worksheets/sheet1.xml. Una segunda hoja se guardaría en xl/worksheets/sheet2.xml y así sucesivamente. Y también cuando existan imágenes, creándose entradas para los tipos de imagen (png, jpg, etc) y para los archivos XML drawing. Esto de las imágenes lo veremos en un tema aparte pues requiere mayor profundidad.

El archivo _rels/.rels contiene las relaciones generales del XLSX. En este caso la única relación será con el libro workbook.xml. Por lo tanto este es un archivo que no es necesario modificar y siempre será el mismo. Vease que las relaciones tienen un identificador Id="rId1" y un objetivo Target="xl/workbook.xml".

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
    <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>
</Relationships>

El archivo xl/_rels/workbook.xml.rels contiene las relaciones internas del libro workbook.xml. Relaciona los archivos que usa el libro: theme1.xml, styles.xml, sharedStrings.xml y worksheets/sheet1.xml. Los tres primeros siempre serán los mismos pues comparten su contenido con todas las hojas. Pero si el libro contiene otra hoja se agregaría otro elemento con archivo worksheets/sheet2.xml e identificador rId5 y así sucesivamente.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
    <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme" Target="theme/theme1.xml"/>
    <Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>
    <Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings" Target="sharedStrings.xml"/>
    <Relationship Id="rId4" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet1.xml"/>
</Relationships>

Si se usan imágenes existirían otros dos archivos de relaciones por cada hoja:

xl/worksheets/_rels/sheet1.xml.rels
xl/drawings/_rels/drawing1.xmls.rels

Como comentamos antes, la inserción de imágenes lo veremos en un tema aparte.

Archivo libro XLSX (workbook)

El archivo xl/workbook.xml contiene la información del libro en el elemento <workbook>.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
    <sheets>
        <sheet state="visible" name="Tab1" sheetId="1" r:id="rId4"/>
    </sheets>
    <definedNames/>
</workbook>

Se observa la única hoja de nuestro ejemplo que tiene por nombre Tab1. El índice es sheetId="1" y la relación es r:id="rId4", información con la que podemos encontrar el archivo de la hoja worksheets/sheet1.xml en el archivo de relaciones internas xl/_rels/workbook.xml.rels que vimos en el apartado anterior.

El elemento <definedNames>, aquí sin contenido, permite definir rangos con nombre, donde podemos denominar con un nombre a una celda o a un rango de celdas y referirnos a ese rango desde cualquier hoja del libro por el nombre. Esto lo veremos en un tema posterior.

Archivo de cadenas compartidas del libro XLSX (sharedStrings)

El archivo xl/sharedStrings.xml contiene una lista de cadenas de texto (strings) incluidas en celdas y compartidas por todas las hojas de cálculos. Su objetivo es no repetir información en el XLSX cuando hay muchas celdas que contienen el mismo texto.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" 
count="2" uniqueCount="2">
    <si>
        <t>a</t>
    </si>
    <si>
        <t>b</t>
    </si>
</sst>

En nuestro ejemplo tenemos dos únicas celdas con los textos "a" y "b" que se trasladan a elementos <si> y a su vez dentro de elementos <t> (texto). En la información de la hoja haremos referencia al string del índice del elemento <si>, índices que se inician en cero.

También es posible incluir texto enriquecido con el elemento <r> en lugar de usar el elemento <t>. Esto lo veremos en un tema posterior.

Archivo de estilos del libro XLSX (styleSheet)

El archivo de estilos xl/styles.xml contiene los estilos y formatos de todas las hojas de cálculo. Se ubica en el elemento <styleSheet>

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
    <numFmts count="0"></numFmts>
    <fonts count="1">
        <font>
            <name val="Arial"/>
            <sz val="12"/>
            <color rgb="FF000000"/>
        </font>
    </fonts>
    <fills count="2">
        <fill>
            <patternFill patternType="none"/>
        </fill>
        <fill>
            <patternFill patternType="gray125"/>
        </fill>
    </fills>
    <borders count="1">
        <border>
            <left/>
            <right/>
            <top/>
            <bottom/>
            <diagonal/>
        </border>
    </borders>
    <cellStyleXfs count="1">
        <xf borderId="0" fillId="0" fontId="0" numFmtId="0" applyAlignment="1" applyFont="1" applyFill="1" applyFormat="0"/>
    </cellStyleXfs>
    <cellXfs count="1">
        <xf borderId="0" fillId="0" fontId="0" numFmtId="0" xfId="0" applyAlignment="1" applyBorder="0" applyFont="1" applyFill="1" applyFormat="0">
            <alignment horizontal="left" vertical="center" shrinkToFit="0" wrapText="1"/>
        </xf>
    </cellXfs>
    <cellStyles count="1">
        <cellStyle xfId="0" name="Normal" builtinId="0"/>
    </cellStyles>
    <dxfs count="0"></dxfs>
</styleSheet>

Es bastante complejo y requiere un tema adicional para explicar sus fundamentos básicos. Por ahora baste decir que en el archivo con datos de una hoja que veremos a continuación, la información de una celda es <c r="A1" t="s" s="0">, donde el atributo s="0" referencia el estilo con índice cero dentro de la lista de estilos contenida en <styleSheet>.

Archivo con datos de una hoja XLSX (worksheet)

El archivo xl/worksheets/sheet1.xml contiene la información para procesar la primera hoja (y única en nuestro ejemplo). Su elemento raíz es <worksheet>.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
    <dimension ref="A1:B1"/>
    <sheetViews>
        <sheetView workbookViewId="0" zoomScaleSheetLayoutView="100" zoomScaleNormal="100" tabSelected="1"></sheetView>
    </sheetViews>
    <sheetFormatPr customHeight="1" defaultRowHeight="15" defaultColWidth="10.6640625"/>
    <cols count="2">
        <col min="1" max="1" hidden="0" customWidth="1" width="10.6640625"/>
        <col min="2" max="2" hidden="0" customWidth="1" width="10.6640625"/>
    </cols>
    <sheetData>
        <row r="1" hidden="0">
            <c r="A1" t="s" s="0">
                <v>0</v>
            </c>
            <c r="B1" t="s" s="0">
                <v>1</v>
            </c>
        </row>
    </sheetData>
</worksheet>

Observamos el elemento <dimension> que declara el rango A1:B1. Las referencias en XLSX son de la forma LN donde L es una o varias letras que referencian la columna y N es el número de fila. Así que el rango A1:B1 son las dos primera celdas en la primera fila. Se trata de exponer el área de datos útiles para que Excel pueda optimizar algunos procesos al cargar el XLSX.

El siguiente elemento <sheetViews> contiene las vistas de la hoja. En XLSX es posible configurar varias vistas para una hoja, pero sólo contemplaremos una primera vista en <sheetView>. Guarda información del escalado o zoom de la hoja así como que hoja será seleccionada. Esto no se contempla en WXTABLE, apareciendo siempre el 100% y la primera hoja seleccionada. En un tema posterior hablaremos de este elemento para configurar el inmovilizado de filas. No es obligatorio incluir el elementos de vistas, en cuyo caso la aplicación usará el 100% y seleccionará la primera hoja.

A continuación se incluye el elemento <sheetFormatPr> que informa del formato predeterminado de la hoja. Y el elemento <cols> que contiene la información de las columnas. Ponemos tantos elementos <col> como columnas tengamos en nuestro WXTABLE. Ambos incluyen información sobre el ancho de columnas y alto de filas, algo que exponemos más ampliamente en un tema siguiente.

Por último se incluyen los valores en el elemento <sheetData>. Observe que se pueden ocultar columnas y también filas con el atributo hidden con valor "1". Aquí aparecen visibles usando hidden con valor cero. Y aunque no sería necesario incluirlo porque es el valor por defecto, he optado por hacerlo para no olvidar en que sitios se ubica ese atributo. El atributo r es la referencia de la fila (elemento <row>) o bien de la celda (elemento <c>). Tenemos <row r="1" hidden="0"> pues sólo hay una fila. En ella hay dos celdas con las referencias "A1" y "B1".

El primer elemento celda es <c r="A1" t="s" s="0">. Recordemos que en la primera celda había una letra "a" y en la segunda una letra "b". Ambos son cadenas (string), por eso el atributo t="s" especifica que el tipo de valor es string. Si el tipo fuera un número pondríamos t="n". En el elemento <v> guardamos el valor si el tipo fuera un número o una referencia a su valor si el tipo es un string. En este caso ambos son string que se almacenan en el elemento <sst> que vimos en el apartado anterior. Las cadenas que contienen se enumeran desde el índice cero. Así que el índice 0 que aparece en el elemento <v>0</v> nos dice que el string a usar será el primero existente en <sst>: la cadena "a".

El atributo s="0" indica que se aplicará el estilo con índice cero que encontraremos en el elemento <styleSheet> del archivo xl/styles.xml que vimos en un apartado anterior. En un tema posterior entraremos más a fondo en los estilos.

Este elemento celda <c> permite otros contenidos como fórmulas, que es preferible presentarlos en un tema aparte dada su complejidad y tras conocer los aspectos básicos de un XLSX mínimo.

El resto de celdas de la tabla están vacías. Realmente no es obligatorio incluir las celdas vacías. Pero dado que Excel procesa información de la primera fila de valores para el cálculo de los anchos de columnas, he decidido siempre incluir todas las celdas de la primera fila obtenida desde WXTABLE, aunque tengan valores vacíos.

El XML anterior conforma el contenido mínimo y obligatorio del elemento <worksheet>, pudiendo incluirse más cosas opcionales, como veremos en otros temas de esta serie, cosas como las siguientes:

  • <mergeCells> para declarar las celdas combinadas.
  • <conditionalFormatting> para declarar el formato condicional.
  • <dataValidations> para declarar la validación de datos.
  • <drawing> para declarar las imágenes.

Archivo de temas del libro XLSX (theme1)

El archivo xl/theme/theme1.xml contiene el tema de formato y estilo predeterminado. Contiene esquemas de color, fuente y formato. Es algo que solo voy a implementar en la parte de fuentes. El resto lo usaré tal y como sale de una hoja de cálculo vacía de GoogleSheet (omitiendo con puntos suspensivos en el siguiente código). En cualquier caso la aplicación WXTABLE tampoco no contempla el usa de temas.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<a:theme name="Sheets" xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
    <a:themeElements>
        <a:clrScheme name="Office">
            ...
        </a:clrScheme>
        <a:fontScheme name="Sheets">
            <a:majorFont>
                <a:latin typeface="Arial"/>
                <a:ea typeface="Arial"/>
                <a:cs typeface="Arial"/>
            </a:majorFont>
            <a:minorFont>
                <a:latin typeface="Arial"/>
                <a:ea typeface="Arial"/>
                <a:cs typeface="Arial"/>
            </a:minorFont>
        </a:fontScheme>
        <a:fmtScheme name="Office">
            ...
        </a:fmtScheme>
    </a:themeElements>
</a:theme>