💡 Ejemplo completo disponible en GitHub:
office-metadata-pii-cleanup-nodejs
Introducción
Un endpoint de carga acepta un DOCX de un miembro del personal y lo almacena en un ticket de cliente. El texto está bien. Las propiedades no: el archivo contiene el nombre de la persona que lo redactó, el colega que lo guardó por última vez, el gerente del departamento del modelo corporativo y, como proviene de SharePoint, el aprobador que lo firmó.
Un saneador de metadatos es un pequeño script que elimina esas propiedades antes de que el archivo se almacene y luego verifica su propio trabajo. Este tutorial crea uno en Node.js con GroupDocs.Metadata, en cuatro pasos: seleccionar propiedades por etiqueta, seleccionarlas por nombre, borrar todo cuando la selectividad deja de ayudar y verificar lo que queda. Cada paso ocupa unas pocas líneas, y el script final tiene menos de cien.
Por qué la sanitización de metadatos es importante
Los datos se acumulan sin que nadie los elija. Word escribe Author y LastSavedBy a partir de la cuenta del sistema operativo en cada guardado, mantiene un contador de revisiones, rastrea TotalEditingTime y registra LastPrinted. Los servidores de documentos añaden rutas de flujo de trabajo, identificadores de aprobadores y URIs de tipo de contenido al hacer check‑in. Nada de esto aparece cuando el documento se lee o se imprime, por lo que una revisión nunca lo detecta.
El objetivo de hacerlo en Node.js en lugar de a mano es que un script devuelve números: cada llamada a eliminación informa cuántas propiedades se eliminaron, y ese recuento puede escribirse en un registro, afirmarse en una prueba o adjuntarse al registro al que pertenece el documento.
Hay una segunda razón, menos evidente hasta que se ejecuta un trabajo por lotes. La limpieza manual es una decisión que se toma una vez por archivo por quien lo maneja, de modo que dos personas saneando el mismo tipo de documento producen resultados diferentes. Un script fija la regla en un solo lugar: las mismas cuatro etiquetas, las mismas listas de subcadenas, aplicadas idénticamente tanto si la cola contiene tres archivos como tres mil.
Requisitos previos
El paquete es Node.js a través de Java, por lo que la máquina necesita un tiempo de ejecución de Java junto con Node.
Instalación
npm install @groupdocs/groupdocs.metadata
El proyecto de ejemplo fija la versión 26.7 y añade una entrada overrides que establece nan a ^2.22.0, lo que mantiene la compilación del enlace nativo en las versiones actuales de Node. Sin un archivo de licencia la biblioteca se ejecuta en modo de evaluación, lo cual es suficiente para seguir cada paso aquí.
Paso 1 - Seleccionar propiedades por lo que significan
Los nombres de las propiedades difieren entre formatos y paquetes, por lo que la primera regla coincide con etiquetas en su lugar. ContainsTagSpecification toma una etiqueta y coincide con cualquier propiedad que la porte; .or() combina especificaciones en una sola.
const T = groupdocs.Tags;
const spec = new groupdocs.ContainsTagSpecification(T.getPerson().getCreator())
.or(new groupdocs.ContainsTagSpecification(T.getPerson().getEditor()))
.or(new groupdocs.ContainsTagSpecification(T.getPerson().getManager()))
.or(new groupdocs.ContainsTagSpecification(T.getCorporate().getCompany()));
const affected = metadata.removeProperties(spec);
metadata.save(outputPath);
Puntos clave:
- Cuatro etiquetas cubren el grupo de identidad: creador, editor, gerente y el campo corporativo de la empresa.
- Título, Asunto y Palabras clave permanecen intactos, de modo que un índice de registros que los usa sigue funcionando.
removePropertiesdevuelve el recuento de elementos afectados en lugar de un booleano.
Envuelve todo en try/finally con metadata.close() en el finally. El enlace mantiene el archivo abierto hasta entonces, y un bucle sin él agota los manejadores.
Paso 2 - Seleccionar propiedades por nombre
Los hilos de comentarios, contadores de revisiones y campos del servidor no llevan etiqueta. Para esos, WithNameSpecification(needle, false) coincide con cualquier propiedad cuyo nombre contenga la aguja, y un constructor de cuatro líneas encadena una por subcadena:
let spec = null;
for (const needle of needles) {
const s = new groupdocs.WithNameSpecification(needle, false /* fullMatch */);
spec = spec ? spec.or(s) : s;
}
return spec;
Tres pasadas reutilizan ese constructor con listas diferentes. Los comentarios van primero:
const affected = metadata.removeProperties(
nameContainsSpec(['Comment', 'Reviewer', 'Reviewed']));
metadata.save(outputPath);
La línea de tiempo de edición es el grupo que tiende a olvidarse, y es el que describe cómo se produjo el documento:
const affected = metadata.removeProperties(nameContainsSpec([
'Revision', 'TrackedChange', 'LastPrinted', 'TotalEditingTime', 'EditTime',
]));
metadata.save(outputPath);
La pasada de SharePoint es la misma llamada con Server, Workflow, Approver, ContentType y Template. La coincidencia por subcadena es deliberada: captura CommentsCount junto a Comment sin mantener una lista de nombres exactos por formato.
Paso 3 - Eliminar todo cuando la selectividad deja de ayudar
Para la copia que abandona la organización, una llamada reemplaza las cuatro pasadas:
const affected = metadata.sanitize();
metadata.save(outputPath);
sanitize() borra todos los paquetes de metadatos que la biblioteca detecta, incluidas partes OOXML personalizadas, y su recuento suele superar la suma de las pasadas dirigidas. También elimina Título y Asunto, por lo que pertenece al límite en lugar de a un bucle de revisión.
Paso 4 - Verificar, porque una omisión silenciosa parece éxito
El escaneo reutiliza las mismas especificaciones mediante findProperties, que lee sin escribir. El resultado es una colección Java, por lo que se recorre por índice:
const props = metadata.findProperties(tagSpec.or(nameSpec));
for (let i = 0; i < props.getCount(); i++) {
const p = props.get_Item(i);
const val = p.getValue && p.getValue();
const value = val ? String(val.getRawValue ? val.getRawValue() : val) : '';
if (!value || value === '0' || value === '0.0') continue;
leaks.push(`${p.getName()}=${value}`);
}
El filtro de vacío y cero se gana su lugar. Lo añadí después de que una ejecución fallara por un contador de revisiones que había sido limpiado a 0, que el escaneo reportaba fielmente como una propiedad sobreviviente.
Ejemplo completo en funcionamiento
El repositorio conecta las seis funciones en index.js, que aplica la licencia, ejecuta cada pasada contra resources/pii-sample.docx, afirma que cada archivo de salida existe y finaliza afirmando que la lista de fugas está vacía. Una aserción fallida sale con un código distinto de cero, de modo que todo funciona como una verificación en CI en lugar de una demo que solo se lee.
Un detalle vale la pena copiar a tu propia versión: cada pasada lee el mismo archivo fuente y escribe una salida separada, en lugar de encadenar un archivo limpiado al siguiente. Eso mantiene los recuentos de afectados independientes, de modo que una línea de registro para la pasada de comentarios informa lo que la regla de comentarios encontró, no lo que quedó después de ejecutar la regla de identidad.
¿Cuándo debería ejecutar una pasada dirigida en lugar de sanitize()?
Siempre que el documento siga en uso. Los archivos que circulan entre revisores dependen de Título, Asunto y Palabras clave para búsqueda y clasificación, y sanitize() elimina los tres junto con los datos personales. Ejecuta las pasadas de identidad y comentarios durante la colaboración, mantén los campos descriptivos intactos y reserva el borrado total para la copia que realmente abandona.
Aplicaciones del mundo real
Manejador de carga
Una ruta de Express sanea un adjunto antes de escribirlo en el almacenamiento, registra los recuentos afectados en el ticket y rechaza la carga cuando la lista de fugas no está vacía.
Trabajo de exportación nocturna
Un trabajador recorre una carpeta de exportación, aplica las pasadas de identidad y servidor, y falla el trabajo en lugar de registrar una advertencia cuando un documento aún informa PII residual.
Puerta de prepublicación
Un paso de compilación sanea los adjuntos de documentación antes del lanzamiento, usando sanitize() porque nada en esos archivos necesita que sus metadatos se conserven.
Mejores prácticas y consejos
- Siempre escribe en una ruta nueva para que el original sobreviva para la resolución de disputas.
- Cierra el objeto de metadatos en un bloque
finally, especialmente en bucles. - Fusiona especificaciones con
.or()en trabajos por lotes; una apertura y un guardado superan a cuatro. - Registra el recuento de afectados por pasada, incluidos los ceros, para que un formato no reconocido sea visible.
Solución de problemas comunes
El recuento de afectados es cero en un documento que sabes está sucio
Verifica que el formato de entrada sea reconocido antes de concluir que el archivo estaba limpio; un archivo ilegible y un archivo limpio producen el mismo cero.
La verificación de fugas informa propiedades que acabas de eliminar
Apúntala al camino de salida guardado, no al de entrada. El escaneo lee el archivo que se le proporciona.
Los globos de comentarios siguen visibles en Word
El texto del comentario vive en el cuerpo del documento, no en un paquete de metadatos. GroupDocs.Metadata elimina las propiedades relacionadas con los comentarios; eliminar los globos en sí requiere una biblioteca de edición de contenido como Aspose.Words.
Conclusión
Cuatro pasos, seis funciones, un script que informa lo que hizo. Las especificaciones de etiquetas manejan el grupo de identidad a través de formatos, las especificaciones de nombre cubren las familias que las etiquetas no clasifican, sanitize() maneja el límite y el escaneo de fugas convierte todo en una verificación. Clona el repositorio, ejecútalo contra un documento que haya pasado por una ronda real de revisión y observa los recuentos antes de decidir qué pasadas necesita tu canalización.