Una introducción informal a DOCX

Publicado: 2022-03-11

Con aproximadamente mil millones de personas que usan Microsoft Office, el formato DOCX es el estándar de facto más popular para intercambiar archivos de documentos entre oficinas. Su competidor más cercano, el formato ODT, solo es compatible con Open/LibreOffice y algunos productos de código abierto, por lo que está lejos de ser estándar. El formato PDF no es un competidor porque los PDF no se pueden editar y no contienen una estructura de documento completa, por lo que solo pueden aceptar cambios locales limitados como marcas de agua, firmas y similares. Esta es la razón por la cual la mayoría de los documentos comerciales se crean en formato DOCX; no hay una buena alternativa para reemplazarlo.

Si bien DOCX es un formato complejo, es posible que desee analizarlo manualmente para tareas más simples, como indexar, convertir a TXT y realizar otras modificaciones pequeñas. Me gustaría darle suficiente información sobre las partes internas de DOCX para que no tenga que hacer referencia a las especificaciones de ECMA, un enorme manual de 5000 páginas.

La mejor manera de comprender el formato es crear un documento simple de una palabra con MSWord y observar cómo la edición del documento cambia el XML subyacente. Se enfrentará a algunos casos en los que DOCX no se formatea correctamente en MS Word y no sabe por qué, o se encontrará con instancias en las que no es evidente cómo generar el formato deseado. Ver y comprender exactamente lo que sucede en el XML ayudará en eso.

Trabajé durante aproximadamente un año en un editor DOCX colaborativo, CollabOffice, y quiero compartir algunos de esos conocimientos con la comunidad de desarrolladores. En este artículo explicaré la estructura de archivos DOCX, resumiendo la información que se encuentra dispersa en Internet. Este artículo es un intermediario entre la enorme y compleja especificación ECMA y los sencillos tutoriales de Internet actualmente disponibles. Puede encontrar los archivos que acompañan a este artículo en el proyecto toptal-docx en mi cuenta de github.

Un archivo DOCX simple

Un archivo DOCX es un archivo ZIP de archivos XML. Si crea un nuevo documento de Microsoft Word vacío, escribe una sola palabra 'Prueba' dentro y descomprime su contenido, verá la siguiente estructura de archivos:

Nuestra nueva estructura DOCX de prueba.

Aunque hemos creado un documento simple, el proceso de guardar en Microsoft Word ha generado temas predeterminados, propiedades del documento, tablas de fuentes, etc., en formato XML.

Todos los archivos dentro de un DOCX son archivos XML, incluso aquellos con la extensión ".rels".
Pío

Para comenzar, eliminemos las cosas no utilizadas y concentrémonos en document.xml , que contiene los elementos de texto principales. Cuando elimine un archivo, asegúrese de haber eliminado todas las referencias de relación a él de otros archivos xml. Aquí hay un ejemplo de diferencia de código sobre cómo he borrado las dependencias de app.xml y core.xml. Si tiene referencias no resueltas o faltantes, MSWord considerará que el archivo está roto.

Aquí está la estructura de nuestro documento DOCX mínimo y simplificado (y aquí está el proyecto en github):

Nuestra estructura DOCX simplificada.

Desglosémoslo por archivo desde aquí, desde arriba:

_rels/.rels

Esto define la referencia que le dice a MS Word dónde buscar el contenido del documento. En este caso, hace referencia a word/document.xml :

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

_rels/documento.xml.rels

Este archivo define referencias a recursos, como imágenes, incrustadas en el contenido del documento. Nuestro documento simple no tiene recursos incrustados, por lo que la etiqueta de relación está vacía:

 <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"> </Relationships>

[Tipos_de_contenido].xml

[Content_Types].xml contiene información sobre los tipos de medios dentro del documento. Como solo tenemos contenido de texto, es bastante simple:

 <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"> <Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/> <Default Extension="xml" ContentType="application/xml"/> <Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/> </Types>

documento.xml

Finalmente, aquí está el XML principal con el contenido de texto del documento. He eliminado algunas de las declaraciones de espacios de nombres para mayor claridad, pero puede encontrar la versión completa del archivo en el proyecto github. En ese archivo encontrará que algunas de las referencias de espacio de nombres en el documento no se usan, pero no debe eliminarlas porque MS Word las necesita.

Aquí está nuestro ejemplo simplificado:

 <w:document> <w:body> <w:pw:rsidR="005F670F" w:rsidRDefault="005F79F5"> <w:r><w:t>Test</w:t></w:r> </w:p> <w:sectPr w:rsidR="005F670F"> <w:pgSz w:w="12240" w:h="15840"/> <w:pgMar w:top="1440" w:right="1440" w:bottom="1440" w:left="1440" w:header="720" w:footer="720" w:gutter="0"/> <w:cols w:space="720"/> <w:docGrid w:linePitch="360"/> </w:sectPr> </w:body> </w:document>

El nodo principal <w:document> representa el documento en sí, <w:body> contiene párrafos y anidadas dentro de <w:body> están las dimensiones de página definidas por <w:sectPr> .

<w:rsidR> es un atributo que puede ignorar; es utilizado por los componentes internos de MS Word.

Echemos un vistazo a un documento más complejo con tres párrafos. He resaltado el XML con los mismos colores en la captura de pantalla de Microsoft Word, para que puedas ver la correlación:

Ejemplo de párrafo complejo con estilo.

<w:pw:rsidR="0081206C" w:rsidRDefault="00E10CAE"> <w:r> <w:t xml:space="preserve">Este es nuestro primer párrafo de ejemplo. Por defecto está alineado a la izquierda, y ahora me gustaría presentar</w:t> </w:r> <w:r> <w:rPr> <w:rFuentes w:ascii="Arial" w:hAnsi="Arial" w:cs="Arial"/> <w:color w:val="000000"/> </w:rPr> <w:t>algo en negrita</w:t> </w:r> <w:r> <w:rPr> <w:rFuentes w:ascii="Arial" w:hAnsi="Arial" w:cs="Arial"/> <w:b/> <w:color w:val="000000"/> </w:rPr> <w:t xml:space="preservar"> texto</w:t> </w:r> <w:r> <w:rPr> <w:rFuentes w:ascii="Arial" w:hAnsi="Arial" w:cs="Arial"/> <w:color w:val="000000"/> </w:rPr> <w:t xml:espacio="preservar">, </w:t> </w:r> <w:pruebaErr w:type="gramStart"/> <w:r> <w:t xml:space="preserve">y también cambie el</w:t> </w:r> <w:rw:rsidRPr="00E10CAE"> <w:rPr><w:rFuentes w:ascii="Impacto" w:hAnsi="Impacto"/> </w:rPr> <w:t>estilo de fuente</w:t> </w:r> <w:r> <w:rPr> <w:rFuentes w:ascii="Impacto" w:hAnsi="Impacto"/> </w:rPr> <w:t xml:space="preservar"> </w:t> </w:r> <w:r> <w:t>a 'Impacto'.</w:t></w:r> </w:p> <w:pw:rsidR="00E10CAE" w:rsidRDefault="00E10CAE"> <w:r> <w:t>Este es un nuevo párrafo.</w:t> </w:r></w:p > <w:pw:rsidR="00E10CAE" w:rsidRPr="00E10CAE" w:rsidRDefault="00E10CAE"> <w:r> <w:t>Este es un párrafo más, un poco más largo.</w:t> </w:r> </w:p>

Estructura del párrafo

Un documento simple consta de párrafos, un párrafo consta de corridas (una serie de texto con la misma fuente, color, etc.) y las corridas consisten en caracteres (como <w:t> ). Las etiquetas <w:t> pueden tener varios caracteres dentro y puede haber algunos en la misma ejecución.

Nuevamente, podemos ignorar <w:rsidR> .

Propiedades del texto

Las propiedades básicas del texto son fuente, tamaño, color, estilo, etc. Hay alrededor de 40 etiquetas que especifican la apariencia del texto. Como puede ver en nuestro ejemplo de tres párrafos, cada ejecución tiene sus propias propiedades dentro <w:rPr> , especificando <w:color> , <w:rFonts> y boldness <w:b> .

Una cosa importante a tener en cuenta es que las propiedades hacen una distinción entre los dos grupos de caracteres, escritura normal y compleja (árabe, por ejemplo), y que las propiedades tienen una etiqueta diferente dependiendo del tipo de carácter al que afecten.

La mayoría de las etiquetas de propiedad de secuencias de comandos normales tienen una etiqueta de secuencia de comandos compleja coincidente con una "C" añadida que especifica que la propiedad es para secuencias de comandos complejas. Por ejemplo: <w:i> (cursiva) se convierte en <w:iCs> , y la etiqueta en negrita para script normal, <w:b> , se convierte en <w:bCs> para script complejo.

Estilos

Hay una barra de herramientas completa en Microsoft Word dedicada a los estilos: normal, sin espacios, encabezado 1, encabezado 2, título, etc. Estos estilos se almacenan en /word/styles.xml (nota: en el primer paso de nuestro ejemplo simple, eliminamos este XML de DOCX. Cree un nuevo DOCX para verlo).

Una vez que haya definido el texto como un estilo, encontrará una referencia a este estilo dentro de la etiqueta de propiedades del párrafo, <w:pPr> . Aquí hay un ejemplo donde he definido mi texto con el estilo Título 1:

 <w:p> <w:pPr> <w:pStyle w:val="Heading1"/> </w:pPr> <w:r> <w:t>My heading 1</w:t> </w:r> </w:p>

y aquí está el estilo en sí mismo de styles.xml :

 <w:style w:type="paragraph" w:style> <w:name w:val="heading 1"/> <w:basedOn w:val="Normal"/> <w:next w:val="Normal"/> <w:link w:val="Heading1Char"/> <w:uiPriority w:val="9"/> <w:qFormat/> <w:rsid w:val="002F7F18"/> <w:pPr> <w:keepNext/> <w:keepLines/> <w:spacing w:before="480" w:after="0"/> <w:outlineLvl w:val="0"/> </w:pPr> <w:rPr> <w:rFonts w:asciiTheme="majorHAnsi" w:eastAsiaTheme="majorEastAsia" w:hAnsiTheme="majorHAnsi" w:cstheme="majorBidi"/> <w:b/> <w:bCs/> <w:color w:val="365F91" w:themeColor="accent1" w:themeShade="BF"/> <w:sz w:val="28"/> <w:szCs w:val="28"/> </w:rPr> </w:style>

El xpath <w:style/w:rPr/w:b> especifica que la fuente está en negrita, y <w:style/w:rPr/w:color> indica el color de la fuente. <w:basedOn> le indica a MSWord que use el estilo "Normal" para cualquier propiedad que falte.

Herencia de propiedad

Las propiedades de texto se heredan. Una ejecución tiene sus propias propiedades ( w:p/w:r/w:rPr/* ), pero también hereda propiedades del párrafo ( w:r/w:pPr/* ), y ambas pueden hacer referencia a las propiedades de estilo de /word/styles.xml .

 <w:r> <w:rPr> <w:rStyle w:val="DefaultParagraphFont"/> <w:sz w:val="16"/> </w:rPr> <w:tab/> </w:r>

Los párrafos y las ejecuciones comienzan con las propiedades predeterminadas: w:styles/w:docDefaults/w:rPrDefault/* y w:styles/w:docDefaults/w:pPrDefault/* . Para obtener el resultado final de las propiedades de un personaje, debes:

  1. Usar propiedades predeterminadas de ejecución/párrafo
  2. Agregar propiedades de estilo de ejecución/párrafo
  3. Agregar propiedades locales de ejecución/párrafo
  4. Agregar propiedades de ejecución de resultados sobre propiedades de párrafo

Cuando digo "añadir" B a A, me refiero a iterar a través de todas las propiedades de B y anular todas las propiedades de A, dejando todas las propiedades que no se cruzan como están.

Un lugar más donde se pueden ubicar las propiedades predeterminadas es en la etiqueta <w:style> con w:type="paragraph" y w:default="1" . Tenga en cuenta que los propios caracteres dentro de una ejecución nunca tienen un estilo predeterminado, por lo que <w:style w:type="character" w:default="1"> en realidad no afecta a ningún texto.

Pío

Los caracteres de una ejecución pueden heredar de su párrafo y ambos pueden heredar de estilos.xml.

1554402290400-dbb29eef3ba6035df7ad726dfc99b2af.png)

Los caracteres de una ejecución pueden heredar de su párrafo y ambos pueden heredar de estilos.xml.

Alternar propiedades

Algunas de las propiedades son propiedades de "alternancia", como <w:b> (negrita) o <w:i> (cursiva); estos atributos se comportan como un operador XOR.

Esto significa que si el estilo principal está en negrita y un estilo secundario está en negrita, el resultado será un texto normal sin negrita.

Debe realizar muchas pruebas e ingeniería inversa para manejar correctamente los atributos de alternancia. Eche un vistazo al párrafo 17.7.3 de la especificación XML abierto ECMA-376 para obtener las reglas formales y detalladas para alternar propiedades/

Las propiedades de alternancia son las más complejas para que un diseñador las maneje correctamente.
Pío

fuentes

Las fuentes siguen las mismas reglas comunes que otros atributos de texto, pero los valores predeterminados de las propiedades de fuente se especifican en un archivo de tema separado, al que se hace referencia en word/_rels/document.xml.rels de esta manera:

 <Relationship Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme" Target="theme/theme1.xml"/>

Según la referencia anterior, el nombre de fuente predeterminado se encontrará en word/theme/themes1.xml , dentro de una etiqueta <a:theme> , a:themeElements/a:fontScheme/a:majorFont o a:minorFont .

El tamaño de fuente predeterminado es 10 a menos que falte la etiqueta w:docDefaults/w:rPrDefault , entonces es tamaño 11.

Alineación del texto

La alineación del texto se especifica mediante una etiqueta <w:jc> con cuatro modos w:val disponibles: "left" , "center" , "right" y "both" .

"left" es el modo predeterminado; el texto comienza a la izquierda del rectángulo del párrafo (generalmente el ancho de la página). (Este párrafo está alineado a la izquierda, lo cual es estándar).

El modo "center" , como era de esperar, centra todos los caracteres dentro del ancho de la página. (Nuevamente, este párrafo ejemplifica la alineación centrada).

En el modo "right" , el texto del párrafo se alinea al margen derecho. (Observe cómo este texto está alineado al lado derecho).

El modo "both" pone espacio adicional entre las palabras para que las líneas se ensanchen y ocupen el ancho completo del párrafo, con la excepción de la última línea que se alinea a la izquierda. (Este párrafo es una demostración de eso.)

Imágenes

DOCX admite dos tipos de imágenes: en línea y flotantes.

Las imágenes en línea aparecen dentro de un párrafo junto con los otros caracteres, se usa <w:drawing> en lugar de usar <w:t> (texto). Puede encontrar la identificación de la imagen con la siguiente sintaxis xpath:

w:drawing/wp:inline/a:graphic/a:graphicData/pic:pic/pic:blipFill/a:blip/@r:embed

La identificación de la imagen se usa para buscar el nombre del archivo en el archivo word/_rels/document.xml.rels , y debe apuntar al archivo gif/jpeg dentro de la subcarpeta word/media. (Vea el archivo word/_rels/document.xml.rels del proyecto github, donde puede ver la ID de la imagen).

Las imágenes flotantes se colocan en relación con los párrafos con texto que fluye a su alrededor. (Aquí está el documento de muestra del proyecto github con una imagen flotante).

Las imágenes flotantes usan <wp:anchor> en lugar de <w:drawing> , por lo que si elimina cualquier texto dentro de <w:p> , tenga cuidado con los anclajes si no desea que se eliminen las imágenes.

En línea vs. flotante.

Las opciones de imagen de MS Word se refieren a la alineación de la imagen como "modo de ajuste de texto".

Mesas

Las etiquetas XML para tablas son similares al marcado de tablas HTML. es lo mismo que <tabla>, coincide con <tr>, etc.

<w:tbl> , la tabla en sí, tiene propiedades de tabla <w:tblPr> , y cada propiedad de columna se presenta mediante <w:gridCol> dentro de <w:tblGrid> . Las filas siguen una por una como etiquetas <w:tr> y cada fila debe tener el mismo número de columnas que se especifica en <w:tblGrid> :

 <w:tbl> <w:tblPr> <w:tblW w:w="5000" w:type="pct" /> </w:tblPr> <w:tblGrid><w:gridCol/><w:gridCol/></w:tblGrid> <w:tr> <w:tc><w:p><w:r><w:t>left</w:t></w:r></w:p></w:tc> <w:tc><w:p><w:r><w:t>right</w:t></w:r></w:p></w:tc> </w:tr> </w:tbl>

El ancho de las columnas de la tabla se puede especificar en la etiqueta <w:tblW> , pero si no lo define, MS Word usará sus algoritmos internos para encontrar el ancho óptimo de las columnas para el tamaño de tabla efectivo más pequeño.

Unidades

Muchos atributos XML dentro de DOCX especifican tamaños o distancias. Si bien son números enteros dentro del XML, todos tienen unidades diferentes, por lo que es necesaria alguna conversión. El tema es complicado, por lo que recomendaría este artículo de Lars Corneliussen sobre unidades en archivos DOCX. La tabla que presenta es útil, aunque con un pequeño error tipográfico: las pulgadas deben ser pt/72, no pt*72.

Aquí hay una hoja de trucos:

CONVERSIONES DE UNIDADES DOCX XML COMUNES
20 de un punto Puntos
dxa/20
Pulgadas
pt/72
centímetros
en*2,54
Fuente tamaño medio
pt/144
EMÚ
en*914400
Ejemplo 11906 595.3 8,27… 21.00086… 4,135 7562088
Etiquetas que usan esto pgSz/pgMar/w:espaciado w: sz wp:extensión, a:extensión

Consejos para implementar un Layouter

Si desea convertir un archivo DOCX (a PDF, por ejemplo), dibujarlo en un lienzo o contar el número de páginas, deberá implementar un diseñador de diseño. Un diseñador de diseño es un algoritmo para calcular las posiciones de los caracteres a partir de un archivo DOCX.

Esta es una tarea compleja si necesita un renderizado con una fidelidad del 100 por ciento. La cantidad de tiempo necesario para implementar un buen maquetador se mide en años-hombre, pero si solo necesita uno simple y limitado, puede hacerlo con relativa rapidez.

Un diseñador de diseño llena un rectángulo principal, que suele ser un rectángulo de la página. Agrega palabras de una corrida una por una. Cuando la línea actual se desborda, comienza una nueva. Si el párrafo es demasiado alto para el rectángulo principal, se ajusta a la página siguiente.

Aquí hay algunas cosas importantes que debe tener en cuenta si decide implementar un diseñador de diseño:

  • El diseñador debe tener cuidado con la alineación del texto y el texto que flota sobre las imágenes.
  • Debe ser capaz de manejar objetos anidados, como tablas anidadas
  • Si desea brindar soporte completo para tales imágenes, deberá implementar un diseñador con al menos dos pasos, el primer paso recopila las posiciones de las imágenes flotantes y el segundo llena el espacio vacío con caracteres de texto.
  • Tenga en cuenta las muescas y los espacios. Cada párrafo tiene espacios antes y después, y estos números se especifican mediante la etiqueta w:spacing . El espaciado vertical se especifica mediante las etiquetas w:after y w:before . Tenga en cuenta que el espacio entre líneas está especificado por w:line , pero este no es el tamaño de la línea como cabría esperar. Para obtener el tamaño de la línea, tome la altura de la fuente actual, multiplíquela por w:line y divídala por 12.
  • Los archivos DOCX no contienen información sobre la paginación. No encontrará el número de páginas en el documento a menos que calcule cuánto espacio necesita para cada línea para determinar el número de páginas. Si necesita encontrar las coordenadas exactas de cada carácter en la página, asegúrese de tener en cuenta todos los espacios, sangrías y tamaños.
  • Si implementa un diseñador de diseño DOCX con todas las funciones que maneja tablas, tenga en cuenta los casos especiales en los que las tablas abarcan varias páginas. Una celda que provoca un desbordamiento de página también afecta a otras celdas.
  • La creación de un algoritmo óptimo para calcular el ancho de las columnas de una tabla es un problema matemático desafiante y los procesadores de texto y los diseñadores suelen usar algunas implementaciones subóptimas. Propongo usar el algoritmo de la documentación de la tabla HTML del W3C como una primera aproximación. No he encontrado una descripción del algoritmo utilizado por MS Word, y Microsoft ha ajustado el algoritmo con el tiempo para que las diferentes versiones de Word puedan diseñar las tablas de forma ligeramente diferente.

Si algo no está claro: ¡haga ingeniería inversa al XML!

Cuando no es obvio cómo funciona esta o aquella etiqueta XML dentro de MS Word, existen dos enfoques principales para resolverlo:

  • Crea el contenido deseado paso a paso. Comience con un archivo docx simple. Guarde cada paso en su propio archivo, como en 1.docx , 2.docx , por ejemplo. Descomprima cada uno de ellos y use una herramienta de comparación visual para comparar carpetas y ver qué etiquetas aparecen después de sus cambios. (Para una opción comercial, pruebe Araxis Merge, o para una opción gratuita, WinMerge).

  • Si genera un archivo DOCX que no le gusta a MS Word, trabaje al revés. Simplifique su XML paso a paso. En algún momento aprenderá qué cambio MS Word encontró incorrecto.

DOCX es bastante complejo, ¿no?

Es complejo y la licencia de Microsoft prohíbe el uso de MS Word en el lado del servidor para procesar DOCX; esto es bastante estándar para productos comerciales. Sin embargo, Microsoft ha proporcionado el archivo XSLT para manejar la mayoría de las etiquetas DOCX, pero no le dará el 100 por ciento o incluso el 99 por ciento de fidelidad. No se admiten procesos como el ajuste de texto sobre imágenes, pero podrá admitir la mayoría de los documentos. (Si no necesita complejidad, considere usar Markdown como alternativa).

Si tiene un presupuesto suficiente (no hay un motor de renderizado DOCX gratuito), puede usar productos comerciales como Aspose o docx4j. La solución gratuita más popular es LibreOffice para convertir entre DOCX y otros formatos, incluido PDF. Desafortunadamente, LibreOffice contiene muchos errores pequeños durante la conversión y, dado que es un producto C++ sofisticado y de código abierto, es lento y difícil de solucionar los problemas de fidelidad.

Alternativamente, si encuentra que el diseño DOCX es demasiado complicado para implementarlo usted mismo, también puede convertirlo a HTML y usar un navegador para representarlo. También puede considerar uno de los desarrolladores XML independientes de Toptal.

Recursos DOCX para lecturas adicionales

  • Especificación ECMA DOCX
  • Biblioteca OpenXML para la manipulación de DOCX desde C#. No contiene información sobre el diseño o el código de representación, pero ofrece una jerarquía de clases que coincide con cada nodo XML posible en DOCX.
  • Siempre puede buscar o preguntar en stackoverflow con palabras clave como docx4j, OpenXML y docx; hay personas en la comunidad que están bien informadas.