Descubre cómo las etiquetas anidadas en PDF, los MCID y los errores de extracción pueden hacer desaparecer texto de un PDF accesible y cómo resolverlos
Ayer estaba solucionando un problema que me reportaron de la extensión de Google Chrome A11ysaurio PDF Checker era un problema que tenía que ver con las etiquetas anidadas en PDF y creo que merece la pena que os lo cuente, porque creo que os va a gustar el tema.
Como ya he dicho muchas veces, la accesibilidad de un documento PDF no depende únicamente de que el texto sea visible o de que el archivo tenga etiquetas, un PDF puede verse perfecto, abrirse correctamente y mostrar todas sus palabras en pantalla, pero un error en la interpretación de su estructura lógica puede hacer que un lector de pantalla reciba solo una parte del contenido y esto es lo que le pasó a la extensión, (y por supuesto también al Plugin de WordPress)
Desarrollando herramientas como A11ysaurio PDF Checker, mi objetivo es analizar si la estructura lógica del documento (el tag tree o árbol de etiquetas) conserva una relación coherente con el contenido de la página. En los documentos reales, esa relación no siempre es sencilla: los generadores de PDF, como Word, InDesign o Acrobat, pueden producir estructuras complejas, anidadas y, en ocasiones, irregulares o para que engañarnos, raras, ¿Cuántas veces habéis visto un árbol de etiquetas que no contiene errores pero que lo miras y te preguntas WTF?
Hoy, quiero compartir algunas de las cosas que he aprendido sobre este tema al solucionar los errores de la extensión, algunas de las lecciones técnicas aprendidas al resolver uno de los problemas más escurridizos de la extracción de texto en PDF: la pérdida del contexto de los MCID al procesar etiquetas anidadas. También os hablaré sobre cómo una mala estrategia recursiva puede producir duplicación acumulativa del texto y cómo diseñar un extractor más resistente frente a documentos imperfectos.
El objetivo no es afirmar que todos los PDF se comporten igual, (ojala, sería mucho más sencillo trabajar con ellos), sino mostrar un tipo de problema que puede aparecer al analizar documentos reales y las decisiones de ingeniería necesarias para no perder información durante el procesamiento.
Tagged PDF, PDF/UA y WCAG no son lo mismo
Antes de entrar en los detalles de implementación, conviene separar tres conceptos que a menudo se mezclan.
- Un Tagged PDF (o PDF etiquetado) contiene una estructura lógica que describe la relación semántica entre el contenido de la página y elementos como párrafos, encabezados, listas, tablas o figuras. La existencia de etiquetas es necesaria para muchas funciones de accesibilidad, pero no garantiza por sí sola que el documento sea accesible.
- PDF/UA es una norma específica para documentos PDF accesibles. Define requisitos sobre la estructura, la semántica, el contenido alternativo, el orden de lectura y otros aspectos del archivo.
- WCAG, por su parte, es un conjunto de pautas de accesibilidad aplicables a contenidos digitales. Puede utilizarse como marco para evaluar documentos PDF, pero no es una sintaxis interna del formato PDF ni sustituye a la especificación del propio formato.
Por eso, en este artículo os hablo de mecanismos internos de los PDF etiquetados que son relevantes al evaluar su accesibilidad y, en determinados contextos, su conformidad con PDF/UA o con criterios relacionados con WCAG.
Qué es un MCID y por qué importa
En un PDF etiquetado, el contenido visible de una página puede estar relacionado con el árbol de estructura lógica mediante identificadores llamados MCID (Marked Content Identifier).
El árbol estructural puede contener elementos como:
- <Document> para representar el documento.
- <H1>, <H2>` y otros encabezados.
- <P> para párrafos.
- <L>, <LI> y elementos relacionados con listas.
- <Table>, <TR>, <TH> y <TD> para tablas.
- <Figure> para contenido gráfico.
- <Span> para fragmentos de texto con propiedades específicas.
En el flujo de contenido de la página pueden aparecer secuencias de contenido marcado. De forma simplificada, el procesador encuentra el inicio de una secuencia, procesa las operaciones gráficas o textuales asociadas y encuentra su cierre.
Conceptualmente, el flujo puede representarse así:
beginMarkedContent(...)
contenido textual o gráfico
endMarkedContent()
El MCID permite relacionar una parte del contenido de la página con un elemento del árbol estructural, esa relación es fundamental para que un extractor o una tecnología de asistencia pueda reconstruir el orden y el significado del contenido.
Sin embargo, no debemos confundir el contenido que se ve en pantalla con el contenido que el extractor puede reconstruir. El visor puede dibujar todas las palabras correctamente aunque la relación entre el flujo de contenido y el árbol estructural sea incompleta o difícil de interpretar.
El problema: pérdida del contexto en etiquetas anidadas
Uno de los problemas que encontramos aparece cuando una herramienta de autoría introduce una etiqueta estructural dentro de otra. Un caso posible es un párrafo que contiene un <Span> utilizado para aplicar una propiedad a una palabra o a un fragmento concreto.
El <Span> no es necesariamente incorrecto, puede utilizarse, por ejemplo, para aplicar propiedades específicas a un fragmento de texto, el problema aparece cuando el extractor no conserva correctamente el contexto del elemento padre o cuando el documento presenta una estructura que la implementación no esperaba.
Un ejemplo simplificado sería el siguiente:
<P> (MCID: 1)
"Este texto es normal pero "
<Span> (sin MCID propio)
"esta palabra tiene una propiedad diferente"
</Span>
" y aquí continúa el párrafo."
</P>
Un analizador básico podría utilizar una única variable para guardar el MCID actual:
let currentMcid = null;
Al entrar en el <P>, el valor podría pasar a ser 1. Pero, al entrar en el <Span>, el parser puede encontrar que no existe un MCID propio y sustituir el valor por null.
El estado terminaría siendo algo parecido a esto:
Inicio de <P> con MCID 1
currentMcid = 1
Inicio de <Span> sin MCID propio
currentMcid = null
Texto del Span
No se encuentra un MCID válido
Texto posterior del párrafo
Tampoco se encuentra un MCID válido
El visor puede seguir mostrando las palabras, pero el extractor puede dejar de asociarlas con la estructura lógica, el resultado sería la pérdida de una palabra, de una frase o incluso de todo el contenido posterior del párrafo.
El problema no es que el extractor haya dejado de leer bytes, el problema es que ha perdido el contexto estructural necesario para saber a qué elemento pertenece el texto.
La solución: representar el estado con una pila
Cuando el contenido puede estar anidado, una única variable no es suficiente, es necesario conservar los estados anteriores para poder restaurarlos al cerrar cada bloque.
Por eso, en el motor del plugin y en la extensión de Chrome de A11ysaurio se utiliza una estructura de pila, o stack, siguiendo el principio LIFO: el último elemento que entra es el primero que sale.
La lógica simplificada es:
- Al iniciar un bloque de contenido marcado, se apila su MCID.
- Si el bloque no tiene un MCID propio, se apila null u otro marcador equivalente.
- Al cerrar el bloque, se desapila el último valor.
- Para extraer texto, se busca desde la parte superior de la pila el último MCID válido disponible.
El estado puede visualizarse así:
Inicio de <P> con MCID 1
Pila: [1]
Inicio de <Span> sin MCID propio
Pila: [1, null]
Texto dentro del <Span>
MCID válido más cercano: 1
Fin de <Span>
Pila: [1]
Texto posterior del párrafo
MCID válido: 1
Fin de <P>
Pila: []
En pseudocódigo:
const mcidStack = [];
function beginMarkedContent(mcid) {
mcidStack.push(Number.isInteger(mcid) ? mcid : null);
}
function endMarkedContent() {
mcidStack.pop();
}
function getNearestValidMcid() {
for (let i = mcidStack.length - 1; i >= 0; i--) {
if (Number.isInteger(mcidStack[i])) {
return mcidStack[i];
}
}
return null;
}
Es importante precisar qué significa esta solución, el extractor no modifica el PDF ni inventa una relación estructural nueva, lo que hace es conservar el contexto válido más cercano para evitar que un bloque decorativo o incompleto provoque la pérdida del texto durante la extracción.
La pila no convierte automáticamente un documento defectuoso en un PDF accesible, permite que el analizador sea más resistente y que pueda representar mejor el contenido disponible, incluso cuando el árbol presenta irregularidades.
Un ejemplo completo de pérdida y recuperación
Imaginemos este párrafo:
<P> (MCID: 12)
"La accesibilidad "
<Span> (sin MCID)
"no termina"
</Span>
" en el contraste de color."
</P>
Con una variable plana, el procesamiento podría producir:
Texto asociado a MCID 12: "La accesibilidad "
Texto sin asociación: "no termina"
Texto sin asociación: " en el contraste de color."
Dependiendo de la implementación, esas dos últimas partes podrían desaparecer del resultado estructurado.
Con una pila, el extractor conserva el contexto del párrafo:
MCID 12: "La accesibilidad no termina en el contraste de color."
De nuevo, esto no significa necesariamente que el <Span> tenga formalmente el MCID 12 en el documento original, significa que, ante la ausencia de un MCID propio, el extractor mantiene el último contexto válido para no perder el contenido.
Otros problemas silenciosos durante la extracción
La pérdida de contexto no es el único problema que puede aparecer al procesar árboles de etiquetas y flujos de contenido, durante la depuración he encontrado otros casos que pueden producir resultados incompletos, duplicados o directamente un error de ejecución.
La trampa recursiva: duplicación acumulativa
Los árboles de etiquetas se procesan con frecuencia mediante funciones recursivas. Esto es natural: un nodo puede tener hijos y cada hijo puede tener otros descendientes.
El problema aparece cuando cada nodo devuelve el texto completo de su subárbol y, después, el nodo padre vuelve a concatenar esos resultados como si fueran contenido nuevo. En lugar de sumar cada fragmento una sola vez, se recorren y reutilizan varias veces las mismas ramas.
Por ejemplo:
<P>
├── Texto directo: "Hola "
└── <Span>
└── Texto directo: "mundo"
Si el <Span> devuelve «mundo» y el <P> incorpora correctamente ese resultado, el texto final es:
Hola mundo
Pero si el padre vuelve a recorrer el <Span> durante una segunda fase y añade otra vez el contenido ya calculado, puede producir:
Hola mundo mundo
En árboles más grandes, el crecimiento puede volverse muy costoso, no siempre será estrictamente exponencial; dependerá de la estrategia concreta de recorrido. Es más preciso hablar de duplicación acumulativa o crecimiento descontrolado del texto extraído.
Una estrategia más segura consiste en separar responsabilidades:
- Un nodo recupera su contenido directo asociado a sus MCID.
- Sus hijos calculan sus propios resultados una sola vez.
- El padre combina esos resultados sin volver a recorrer las mismas ramas.
- El orden de combinación debe respetar la secuencia estructural esperada.
En la práctica, puede ser útil calcular el resultado de cada nodo una vez y almacenarlo temporalmente, siempre que se tenga cuidado con el orden de lectura y con los nodos que no representan contenido textual.
ActualText vacío o compuesto solo por espacios
ActualText puede proporcionar una representación textual alternativa para contenido asociado a una secuencia marcada o a un elemento estructural, cuando se utiliza correctamente, puede ser muy útil para representar de forma más adecuada ciertos símbolos, ligaduras, fórmulas o fragmentos cuyo texto visual no sea suficiente.
Sin embargo, algunos documentos contienen propiedades presentes pero vacías o compuestas únicamente por espacios, por ejemplo:
ActualText = " "
Si el extractor interpreta cualquier valor presente como sustitución obligatoria, puede reemplazar texto válido por una cadena vacía.
Una política defensiva razonable es limpiar el valor antes de utilizarlo:
const cleanedActualText = actualText?.trim() ?? '';
if (cleanedActualText !== '') {
return cleanedActualText;
}
return structuralText;
Esta decisión debe entenderse como una estrategia de robustez del extractor, no todos los casos pueden resolverse únicamente comprobando si la propiedad existe; también hay que valorar si contiene información utilizable.
Alt no es simplemente otro ActualText
Alt y ActualText no son sinónimos.
Alt se utiliza principalmente para proporcionar una descripción alternativa de contenido no textual, como una figura. ActualText, en cambio, puede proporcionar una representación textual alternativa del contenido asociado a un elemento o a una secuencia marcada.
Por tanto, un extractor no debería tratar automáticamente cualquier valor Alt como sustituto del texto ordinario de cualquier nodo. Antes debe conocer el tipo de elemento, el contexto estructural y la semántica que tiene ese atributo.
Una figura podría representarse conceptualmente así:
<Figure>
Alt: "Gráfico que muestra el crecimiento de las ventas entre 2023 y 2025"
</Figure>
En este caso, Alt describe el contenido gráfico. No es una versión alternativa de un párrafo que contenga texto visible.
MCID numéricos y formas variables de los objetos
Otra fuente de errores aparece cuando la biblioteca utilizada para leer el PDF devuelve los hijos del árbol con representaciones diferentes.
Según la capa de procesamiento, un hijo puede aparecer como:
- Un diccionario u objeto estructural.
- Una referencia indirecta.
- Un número entero que representa un MCID.
- Una abstracción específica de la biblioteca utilizada.
Si el extractor presupone que todos los hijos tienen propiedades como id, type o role, puede producir una excepción cuando recibe un entero:
const role = child.role;
Una implementación defensiva debe comprobar primero el tipo y la forma del dato:
function normalizeChild(child) {
if (Number.isInteger(child)) {
return {
role: 'Unknown',
mcid: child,
sourceType: 'raw-mcid'
};
}
if (child && typeof child === 'object') {
return child;
}
return null;
}
La conversión a un nodo virtual puede ser útil para que el pipeline no se detenga, pero hay que mantener la distinción entre un MCID válido, un nodo estructural y un valor no reconocido. La tolerancia a entradas imperfectas no debe convertirse en una forma de ocultar errores de interpretación.
Qué implica esto para la auditoría de accesibilidad
Estos problemas muestran por qué la auditoría automática de un PDF no debería reducirse a comprobar si existe un árbol de etiquetas.
Un documento puede tener etiquetas y, aun así, presentar problemas como:
- Contenido visual que no se puede asociar correctamente con el árbol estructural.
- Fragmentos que desaparecen durante la extracción.
- Texto duplicado en el resultado accesible.
- Atributos alternativos vacíos que ocultan información válida.
- Nodos que provocan errores de ejecución en el analizador.
- Orden de lectura incorrecto por una combinación inadecuada de nodos.
Por eso, una herramienta de evaluación debería analizar al menos tres capas relacionadas:
- La forma y la jerarquía del árbol estructural.
- La relación entre el contenido marcado y los MCID.
- El texto resultante y su orden de lectura.
Ninguna de estas capas es suficiente por sí sola. El árbol puede parecer correcto mientras el flujo de contenido no se procesa bien. El texto extraído puede parecer completo mientras la semántica de los nodos es incorrecta. Y un validador sintáctico puede no detectar un problema que solo se manifiesta al reconstruir la experiencia de lectura.
Buenas prácticas para creadores de contenido
Aunque buena parte del problema corresponde a las herramientas de procesamiento, las decisiones de autoría también pueden influir en la complejidad del PDF final.
Evitar el formateo innecesario
El formato aplicado carácter por carácter puede generar numerosos fragmentos y etiquetas <Span>. No todos son incorrectos, pero una estructura excesivamente fragmentada dificulta la interpretación y aumenta la superficie de posibles errores.
Siempre que sea posible:
- Utiliza estilos de párrafo y carácter coherentes.
- Evita aplicar formatos independientes a cada palabra o letra sin necesidad.
- Comprueba que los cambios visuales no alteren el orden de lectura.
- Revisa el árbol de etiquetas después de exportar el PDF.
Revisar textos alternativos y textos de reemplazo
Los campos Alt y ActualText deben contener información con una finalidad clara, conviene comprobar que no incluyen únicamente espacios, saltos de línea o caracteres invisibles.
También es importante no utilizar texto alternativo para repetir innecesariamente información que ya está disponible para la persona lectora. Una descripción alternativa debe aportar el significado relevante del contenido no textual, no generar ruido.
Validar el resultado exportado
El archivo editable no es el producto final, el proceso de exportación puede modificar la estructura, dividir fragmentos de texto o introducir nodos adicionales.
Por eso, la comprobación debe hacerse sobre el PDF que realmente se distribuirá, no solo sobre el documento original de Word, InDesign u otra herramienta de autoría.
Buenas prácticas para desarrolladores
Los analizadores de PDF trabajan con documentos producidos por herramientas y versiones muy diferentes, la implementación debe asumir que encontrará variaciones y estructuras imperfectas.
No confiar ciegamente en el árbol
Un árbol bien formado visualmente no garantiza que todos los vínculos con el contenido de página sean válidos. Hay que validar referencias, tipos de datos, MCID y contenido asociado.
Tratar el estado como jerárquico
Siempre que el contenido pueda anidarse, el estado debe conservarse con una estructura adecuada: una pila, un contexto inmutable o una estrategia equivalente. Una variable plana no permite restaurar correctamente los estados anteriores.
Evitar recorrer varias veces el mismo subárbol
Los documentos extensos pueden contener miles de nodos. Una función recursiva que repita el mismo recorrido puede consumir mucha memoria, duplicar el resultado o bloquear el navegador.
Conviene:
- Separar el contenido directo del contenido descendiente.
- Cachear resultados cuando sea seguro hacerlo.
- Detectar ciclos o referencias inesperadas.
- Limitar la profundidad si la biblioteca puede devolver estructuras anómalas.
- Registrar los casos que no se puedan interpretar.
Diseñar para la observabilidad
Cuando una herramienta descarta texto o utiliza un valor heredado, debería poder explicar por qué. Los registros de depuración pueden incluir:
- La página procesada.
- La secuencia de apertura y cierre de contenido marcado.
- El contenido de la pila de MCID.
- El nodo estructural asociado.
- El motivo por el que se utilizó ActualText, Alt o el texto visual.
Esta información es especialmente importante cuando el resultado de la herramienta debe convertirse en una decisión de remediación.
En resumen…
Los problemas más difíciles de la accesibilidad en PDF no siempre se encuentran en lo que se ve en pantalla, a menudo aparecen en la relación entre varias representaciones del mismo documento: el flujo de contenido, las secuencias de contenido marcado, los MCID y el árbol estructural.
Una etiqueta anidada sin MCID propio no debería hacer que el extractor olvide el contexto de su padre, un atributo vacío no debería ocultar texto válido, un nodo representado como un entero no debería provocar el cierre inesperado de todo el análisis. Y una función recursiva no debería añadir varias veces el mismo fragmento.
La solución no consiste en asumir que todos los PDF están perfectamente construidos, (casi nunca lo están), consiste en diseñar analizadores capaces de trabajar con la complejidad y las irregularidades de los documentos reales:
- Utilizar pilas para conservar el contexto jerárquico.
- Distinguir entre texto directo, texto descendiente y texto alternativo.
- Validar los tipos de datos antes de acceder a sus propiedades.
- Tratar ActualText y Alt según su finalidad y su contexto.
- Evitar recorridos recursivos redundantes.
- Registrar las decisiones tomadas durante la extracción.
Al comprender y mitigar estos problemas, A11ysaurio PDF Checker puede analizar con mayor precisión documentos complejos y ayudar a detectar fallos que no son evidentes mediante una inspección visual, además, yo he aprendido un montón y os lo cuento, pero si os tenéis que quedar con una sola frase de este artículo es la siguiente:
No os fieis ciegamente de ninguna herramienta
La accesibilidad real no consiste únicamente en que una persona pueda ver todas las palabras, sino en que la estructura, el contenido y el orden de lectura lleguen de forma coherente a las tecnologías de asistencia.

