Extraiga texto de documentos de MS Word en Java

La extracción de texto de documentos de Word a menudo se realiza en diferentes escenarios. Por ejemplo, para analizar el texto, extraer secciones particulares de un documento y combinarlas en un solo documento, etc. En este artículo, aprenderá cómo extraer texto de documentos de Word mediante programación en Java. Además, cubriremos cómo extraer contenido entre elementos específicos como párrafos, tablas, etc. dinámicamente.

Biblioteca Java para extraer texto de documentos de Word

Aspose.Words for Java es una poderosa biblioteca que le permite crear documentos de MS Word desde cero. Además, le permite manipular los documentos de Word existentes para el cifrado, la conversión, la extracción de texto, etc. Usaremos esta biblioteca para extraer texto de los documentos DOCX o DOC de Word. Puede descargar el JAR de la API o instalarlo usando las siguientes configuraciones de Maven.

<repository>
    <id>AsposeJavaAPI</id>
    <name>Aspose Java API</name>
    <url>https://repository.aspose.com/repo/</url>
</repository>
<dependency>
    <groupId>com.aspose</groupId>
    <artifactId>aspose-words</artifactId>
    <version>22.6</version>
    <type>pom</type>
</dependency>

Extracción de texto en Word DOC/DOCX en Java

Un documento de MS Word consta de varios elementos, que incluyen párrafos, tablas, imágenes, etc. Por lo tanto, los requisitos de extracción de texto pueden variar de un escenario a otro. Por ejemplo, es posible que necesite extraer texto entre párrafos, marcadores, comentarios, etc.

Cada tipo de elemento en un DOC/DOCX de Word se representa como un nodo. Por tanto, para procesar un documento, tendrás que jugar con los nodos. Entonces, comencemos y veamos cómo extraer texto de documentos de Word en diferentes escenarios.

Extraer texto de un DOC de Word en Java

En esta sección, vamos a implementar un extractor de texto Java para documentos de Word y el flujo de trabajo de extracción de texto sería el siguiente:

  • Primero, definiremos los nodos que queremos incluir en el proceso de extracción de texto.
  • Luego, extraeremos el contenido entre los nodos especificados (incluyendo o excluyendo los nodos inicial y final).
  • Finalmente, utilizaremos el clon de los nodos extraídos, por ejemplo, para crear un nuevo documento de Word con contenido extraído.

Ahora escribamos un método llamado extractContent al que le pasaremos los nodos y algunos otros parámetros para realizar la extracción de texto. Este método analizará el documento y clonará los nodos. Los siguientes son los parámetros que pasaremos a este método.

  1. startNode y endNode como puntos de inicio y fin para la extracción del contenido, respectivamente. Estos pueden ser nodos de nivel de bloque (Párrafo, Tabla) o de nivel en línea (por ejemplo, Ejecutar, FieldStart, BookmarkStart, etc.).
    1. Para pasar un campo, debe pasar el objeto FieldStart correspondiente.
    2. Para pasar marcadores, se deben pasar los nodos BookmarkStart y BookmarkEnd.
    3. Para los comentarios, se deben utilizar los nodos CommentRangeStart y CommentRangeEnd.
  2. isInclusive define si los marcadores se incluyen en la extracción o no. Si esta opción se establece en falso y se pasa el mismo nodo o nodos consecutivos, se devolverá una lista vacía.

La siguiente es la implementación completa del método extractContent que extrae el contenido entre los nodos que se pasan.

// Para obtener ejemplos completos y archivos de datos, vaya a https://github.com/aspose-words/Aspose.Words-for-Java
public static ArrayList extractContent(Node startNode, Node endNode, boolean isInclusive) throws Exception {
    // Primero verifique que los nodos pasados a este método sean válidos para su uso.
    verifyParameterNodes(startNode, endNode);

    // Cree una lista para almacenar los nodos extraídos.
    ArrayList nodes = new ArrayList();

    // Mantenga un registro de los nodos originales pasados a este método para que podamos dividir los nodos de marcador si es necesario.
    Node originalStartNode = startNode;
    Node originalEndNode = endNode;

    // Extraiga contenido basado en nodos de nivel de bloque (párrafos y tablas). Atraviese los nodos principales para encontrarlos.
    // Dividiremos el contenido del primer y último nodo dependiendo de si los nodos marcadores están en línea
    while (startNode.getParentNode().getNodeType() != NodeType.BODY)
        startNode = startNode.getParentNode();

    while (endNode.getParentNode().getNodeType() != NodeType.BODY)
        endNode = endNode.getParentNode();

    boolean isExtracting = true;
    boolean isStartingNode = true;
    boolean isEndingNode;
    // El nodo actual que estamos extrayendo del documento.
    Node currNode = startNode;

    // Comience a extraer contenido. Procese todos los nodos de nivel de bloque y divida específicamente el primer y el último nodo cuando sea necesario para conservar el formato del párrafo.
    // El método es un poco más complejo que un extractor normal, ya que debemos tener en cuenta la extracción utilizando nodos en línea, campos, marcadores, etc. para que sea realmente útil.
    while (isExtracting) {
        // Clona el nodo actual y sus hijos para obtener una copia.
        /*System.out.println(currNode.getNodeType());
        if(currNode.getNodeType() == NodeType.EDITABLE_RANGE_START
                || currNode.getNodeType() == NodeType.EDITABLE_RANGE_END)
        {
            currNode = currNode.nextPreOrder(currNode.getDocument());
        }*/
        System.out.println(currNode);
        System.out.println(endNode);

        CompositeNode cloneNode = null;
        ///cloneNode = (CompositeNode) currNode.deepClone(true);

        Node inlineNode = null;
        if(currNode.isComposite())
        {
            cloneNode = (CompositeNode) currNode.deepClone(true);
        }
        else
        {
            if(currNode.getNodeType() == NodeType.BOOKMARK_END)
            {
                Paragraph paragraph = new Paragraph(currNode.getDocument());
                paragraph.getChildNodes().add(currNode.deepClone(true));
                cloneNode = (CompositeNode)paragraph.deepClone(true);
            }
        }

        isEndingNode = currNode.equals(endNode);

        if (isStartingNode || isEndingNode) {
            // Necesitamos procesar cada marcador por separado, así que páselo a un método separado.
            if (isStartingNode) {
                processMarker(cloneNode, nodes, originalStartNode, isInclusive, isStartingNode, isEndingNode);
                isStartingNode = false;
            }

            // El condicional debe estar separado ya que los marcadores de inicio y fin del nivel de bloque pueden ser el mismo nodo.
            if (isEndingNode) {
                processMarker(cloneNode, nodes, originalEndNode, isInclusive, isStartingNode, isEndingNode);
                isExtracting = false;
            }
        } else
            // El nodo no es un marcador de inicio o final, simplemente agregue la copia a la lista.
            nodes.add(cloneNode);

        // Vaya al siguiente nodo y extráigalo. Si el siguiente nodo es nulo, significa que el resto del contenido se encuentra en una sección diferente.
        if (currNode.getNextSibling() == null && isExtracting) {
            // Pase a la siguiente sección.
            Section nextSection = (Section) currNode.getAncestor(NodeType.SECTION).getNextSibling();
            currNode = nextSection.getBody().getFirstChild();
        } else {
            // Mover al siguiente nodo en el cuerpo.
            currNode = currNode.getNextSibling();
        }
    }

    // Devuelve los nodos entre los marcadores de nodos.
    return nodes;
}

El método extractContent también requiere algunos métodos auxiliares para realizar la operación de extracción de texto, que se indican a continuación.

/**
 * Comprueba que los parámetros de entrada son correctos y se pueden utilizar. Lanza una excepción
 * si hay algún problema.
 */
private static void verifyParameterNodes(Node startNode, Node endNode) throws Exception {
	// El orden en que se realizan estas comprobaciones es importante.
	if (startNode == null)
		throw new IllegalArgumentException("Start node cannot be null");
	if (endNode == null)
		throw new IllegalArgumentException("End node cannot be null");

	if (!startNode.getDocument().equals(endNode.getDocument()))
		throw new IllegalArgumentException("Start node and end node must belong to the same document");

	if (startNode.getAncestor(NodeType.BODY) == null || endNode.getAncestor(NodeType.BODY) == null)
		throw new IllegalArgumentException("Start node and end node must be a child or descendant of a body");

	// Verifique que el nodo final esté después del nodo inicial en el árbol DOM
	// Primero verifique si están en diferentes secciones, luego si no lo están, verifique
	// su posición en el cuerpo de la misma sección en la que se encuentran.
	Section startSection = (Section) startNode.getAncestor(NodeType.SECTION);
	Section endSection = (Section) endNode.getAncestor(NodeType.SECTION);

	int startIndex = startSection.getParentNode().indexOf(startSection);
	int endIndex = endSection.getParentNode().indexOf(endSection);

	if (startIndex == endIndex) {
		if (startSection.getBody().indexOf(startNode) > endSection.getBody().indexOf(endNode))
			throw new IllegalArgumentException("The end node must be after the start node in the body");
	} else if (startIndex > endIndex)
		throw new IllegalArgumentException("The section of end node must be after the section start node");
}

/**
 * Comprueba si un nodo aprobado es un nodo en línea.
 */
private static boolean isInline(Node node) throws Exception {
	// Prueba si el nodo es descendiente de un nodo Párrafo o Tabla y tampoco es un
	// párrafo o una tabla un párrafo dentro de una clase de comentario que es descendiente de
	// un párrafo es posible.
	return ((node.getAncestor(NodeType.PARAGRAPH) != null || node.getAncestor(NodeType.TABLE) != null)
			&& !(node.getNodeType() == NodeType.PARAGRAPH || node.getNodeType() == NodeType.TABLE));
}

/**
 * Elimina el contenido antes o después del marcador en el nodo clonado según
 * en el tipo de marcador.
 */
private static void processMarker(CompositeNode cloneNode, ArrayList nodes, Node node, boolean isInclusive,
		boolean isStartMarker, boolean isEndMarker) throws Exception {
	// Si estamos tratando con un nodo de nivel de bloque, solo vea si debe incluirse
	// y añádelo a la lista.
	if (!isInline(node)) {
		// No agregue el nodo dos veces si los marcadores son el mismo nodo
		if (!(isStartMarker && isEndMarker)) {
			if (isInclusive)
				nodes.add(cloneNode);
		}
		return;
	}

	// Si un marcador es un nodo FieldStart, compruebe si debe incluirse o no.
	// Asumimos por simplicidad que FieldStart y FieldEnd aparecen en el mismo
	// párrafo.
	if (node.getNodeType() == NodeType.FIELD_START) {
		// Si el marcador es un nodo de inicio y no se incluye, salte al final de
		// el campo.
		// Si el marcador es un nodo final y debe incluirse, muévase al final
		// campo para que el campo no se elimine.
		if ((isStartMarker && !isInclusive) || (!isStartMarker && isInclusive)) {
			while (node.getNextSibling() != null && node.getNodeType() != NodeType.FIELD_END)
				node = node.getNextSibling();

		}
	}

	// Si alguno de los marcadores es parte de un comentario, para incluir el comentario mismo,
	// necesita mover el puntero hacia adelante hasta el comentario
	// nodo encontrado después del nodo CommentRangeEnd.
	if (node.getNodeType() == NodeType.COMMENT_RANGE_END) {
		while (node.getNextSibling() != null && node.getNodeType() != NodeType.COMMENT)
			node = node.getNextSibling();

	}

	// Encuentre el nodo correspondiente en nuestro nodo clonado por índice y devuélvalo.
	// Si el nodo inicial y final son los mismos, es posible que algunos nodos secundarios ya tengan
	// sido eliminado Restar el
	// diferencia para obtener el índice correcto.
	int indexDiff = node.getParentNode().getChildNodes().getCount() - cloneNode.getChildNodes().getCount();

	// Recuento de nodos secundarios idéntico.
	if (indexDiff == 0)
		node = cloneNode.getChildNodes().get(node.getParentNode().indexOf(node));
	else
		node = cloneNode.getChildNodes().get(node.getParentNode().indexOf(node) - indexDiff);

	// Retire los nodos hasta/desde el marcador.
	boolean isSkip;
	boolean isProcessing = true;
	boolean isRemoving = isStartMarker;
	Node nextNode = cloneNode.getFirstChild();

	while (isProcessing && nextNode != null) {
		Node currentNode = nextNode;
		isSkip = false;

		if (currentNode.equals(node)) {
			if (isStartMarker) {
				isProcessing = false;
				if (isInclusive)
					isRemoving = false;
			} else {
				isRemoving = true;
				if (isInclusive)
					isSkip = true;
			}
		}

		nextNode = nextNode.getNextSibling();
		if (isRemoving && !isSkip)
			currentNode.remove();
	}

	// Después del procesamiento, el nodo compuesto puede quedar vacío. Si tiene no incluye
	// eso.
	if (!(isStartMarker && isEndMarker)) {
		if (cloneNode.hasChildNodes())
			nodes.add(cloneNode);
	}
}

public static Document generateDocument(Document srcDoc, ArrayList nodes) throws Exception {

	// Crea un documento en blanco.
	Document dstDoc = new Document();
	// Elimina el primer párrafo del documento vacío.
	dstDoc.getFirstSection().getBody().removeAllChildren();

	// Importe cada nodo de la lista al nuevo documento. mantener el original
	// formateo del nodo.
	NodeImporter importer = new NodeImporter(srcDoc, dstDoc, ImportFormatMode.KEEP_SOURCE_FORMATTING);

	for (Node node : (Iterable<Node>) nodes) {
		Node importNode = importer.importNode(node, true);
		dstDoc.getFirstSection().getBody().appendChild(importNode);
	}

	// Devolver el documento generado.
	return dstDoc;
}

Ahora estamos listos para utilizar estos métodos y extraer texto de un documento de Word.

Java Extrae texto entre párrafos en un DOC de Word

Veamos cómo extraer contenido entre dos párrafos en un documento DOCX de Word. Los siguientes son los pasos para realizar esta operación en Java.

  • Primero, cargue el documento de Word usando la clase Document.
  • Obtenga la referencia de los párrafos inicial y final en dos objetos mediante el método Document.getFirstSection().getChild(NodeType.PARAGRAPH, int, bool).
  • Llame al método extractContent(startPara, endPara, true) para extraer los nodos en un objeto.
  • Llame al método auxiliar generateDocument(Document, extractNodes) para crear un documento que consista en el contenido extraído.
  • Finalmente, guarde el documento devuelto usando el método Document.save(String).

El siguiente ejemplo de código muestra cómo extraer texto entre los párrafos 7 y 11 en un DOCX de Word en Java.

// Cargar documento
Document doc = new Document("TestFile.doc");

// Reúna los nodos. El método GetChild utiliza un índice basado en 0
Paragraph startPara = (Paragraph) doc.getFirstSection().getChild(NodeType.PARAGRAPH, 6, true);
Paragraph endPara = (Paragraph) doc.getFirstSection().getChild(NodeType.PARAGRAPH, 10, true);
// Extraiga el contenido entre estos nodos en el documento. incluir estos
// marcadores en la extracción.
ArrayList extractedNodes = extractContent(startPara, endPara, true);

// Inserte el contenido en un nuevo documento separado y guárdelo en el disco.
Document dstDoc = generateDocument(doc, extractedNodes);
dstDoc.save("output.doc");

Java Extraer texto de DOC - Entre diferentes tipos de nodos

También puede extraer contenido entre diferentes tipos de nodos. Como demostración, extraigamos el contenido entre un párrafo y una tabla y guárdelo en un nuevo documento de Word. Los siguientes son los pasos para extraer texto entre diferentes nodos en un documento de Word en Java.

  • Cargue el documento de Word usando la clase Document.
  • Obtenga la referencia de los nodos inicial y final en dos objetos mediante el método Document.getFirstSection().getChild(NodeType, int, bool).
  • Llame al método extractContent(startPara, endPara, true) para extraer los nodos en un objeto.
  • Llame al método auxiliar generateDocument(Document, extractNodes) para crear un documento que consista en el contenido extraído.
  • Guarde el documento devuelto utilizando el método Document.save(String).

El siguiente ejemplo de código muestra cómo extraer texto entre un párrafo y una tabla en un DOCX usando Java.

// Cargar documentos
Document doc = new Document("TestFile.doc");

// Obtener la referencia del párrafo inicial
Paragraph startPara = (Paragraph) doc.getLastSection().getChild(NodeType.PARAGRAPH, 2, true);
Table endTable = (Table) doc.getLastSection().getChild(NodeType.TABLE, 0, true);

// Extraiga el contenido entre estos nodos en el documento. Incluya estos marcadores en la extracción.
ArrayList extractedNodes = extractContent(startPara, endTable, true);

// Invirtamos la matriz para facilitar la inserción del contenido en el documento.
Collections.reverse(extractedNodes);

while (extractedNodes.size() > 0) {
    // Insertar el último nodo de la lista invertida
    endTable.getParentNode().insertAfter((Node) extractedNodes.get(0), endTable);
    // Quite este nodo de la lista después de la inserción.
    extractedNodes.remove(0);
}

// Guarde el documento generado en el disco.
doc.save("output.doc");

Java Extrayendo texto de DOCX - Entre párrafos basado en estilos

Veamos ahora cómo extraer contenido entre párrafos según los estilos. Para la demostración, vamos a extraer contenido entre el primer “Título 1” y el primer “Título 3” en el documento de Word. Los siguientes pasos demuestran cómo lograr esto en Java.

  • Primero, cargue el documento de Word usando la clase Document.
  • Luego, extraiga los párrafos en un objeto usando el método auxiliar de párrafosByStyleName(Document, “Heading 1”).
  • Extraiga párrafos en otro objeto usando el método auxiliar de párrafosByStyleName(Document, “Heading 3”).
  • Llame al método extractContent(startPara, endPara, true) y pase los primeros elementos en ambas matrices de párrafos como primer y segundo parámetro.
  • Llame al método auxiliar generateDocument(Document, extractNodes) para crear un documento que consista en el contenido extraído.
  • Finalmente, guarde el documento devuelto usando el método Document.save(String).

El siguiente ejemplo de código muestra cómo extraer contenido entre párrafos en función de los estilos.

// Cargar documento
Document doc = new Document(dataDir + "TestFile.doc");

// Reúna una lista de los párrafos utilizando los estilos de título respectivos.
ArrayList parasStyleHeading1 = paragraphsByStyleName(doc, "Heading 1");
ArrayList parasStyleHeading3 = paragraphsByStyleName(doc, "Heading 3");

// Utilice la primera instancia de los párrafos con esos estilos.
Node startPara1 = (Node) parasStyleHeading1.get(0);
Node endPara1 = (Node) parasStyleHeading3.get(0);

// Extraiga el contenido entre estos nodos en el documento. No incluya estos marcadores en la extracción.
ArrayList extractedNodes = extractContent(startPara1, endPara1, false);

// Inserte el contenido en un nuevo documento separado y guárdelo en el disco.
Document dstDoc = generateDocument(doc, extractedNodes);
dstDoc.save("output.doc");

Extractor de texto de Word Java - Leer más

Puede explorar otros escenarios de extracción de texto de documentos de Word utilizando este artículo de documentación.

API de Java para extraer texto de DOC/DOCX: obtenga una licencia gratuita

Puede obtener una licencia temporal para usar Aspose.Words for Java sin limitaciones de evaluación.

Conclusión

En este artículo, ha aprendido cómo extraer texto de MS Word DOC DOCX en Java. Además, ha visto cómo extraer contenido entre tipos de nodos similares o diferentes en un documento de Word mediante programación. Por lo tanto, puede crear su propio extractor de texto de MS Word en Java. Además, puede explorar otras características de Aspose.Words for Java utilizando la documentación. En caso de que tenga alguna pregunta, no dude en hacérnosla saber a través de nuestro foro.

Ver también