Extraiga texto de documentos de MS Word en C#

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 C#. Además, cubriremos cómo extraer contenido entre elementos específicos como párrafos, tablas, etc. dinámicamente.

Biblioteca C# para extraer texto de documentos de Word

Aspose.Words for .NET 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 la DLL de la API o instalarla directamente desde NuGet mediante la consola del administrador de paquetes.

PM> Install-Package Aspose.Words

Extracción de texto en documentos de Word usando C#

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 documento 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 documento de Word en C#

En esta sección, vamos a implementar un extractor de texto C# 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.

public static ArrayList ExtractContent(Node startNode, Node endNode, bool isInclusive)
{
    // 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.ParentNode.NodeType != NodeType.Body)
        startNode = startNode.ParentNode;

    while (endNode.ParentNode.NodeType != NodeType.Body)
        endNode = endNode.ParentNode;

    bool isExtracting = true;
    bool isStartingNode = true;
    bool isEndingNode = false;
    // 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.
        Node cloneNode = currNode.Clone(true);
        isEndingNode = currNode.Equals(endNode);

        if ((isStartingNode || isEndingNode) && cloneNode.IsComposite)
        {
            // Necesitamos procesar cada marcador por separado, así que páselo a un método separado.
            if (isStartingNode)
            {
                ProcessMarker((CompositeNode)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((CompositeNode)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.NextSibling == null && isExtracting)
        {
            // Pase a la siguiente sección.
            Section nextSection = (Section)currNode.GetAncestor(NodeType.Section).NextSibling;
            currNode = nextSection.Body.FirstChild;
        }
        else
        {
            // Mover al siguiente nodo en el cuerpo.
            currNode = currNode.NextSibling;
        }
    }

    // Devuelve los nodos entre los marcadores de nodo.
    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.

public static List<Paragraph> ParagraphsByStyleName(Document doc, string styleName)
{
    // Cree una matriz para recopilar párrafos del estilo especificado.
    List<Paragraph> paragraphsWithStyle = new List<Paragraph>();

    NodeCollection paragraphs = doc.GetChildNodes(NodeType.Paragraph, true);

    // Examine todos los párrafos para encontrar aquellos con el estilo especificado.
    foreach (Paragraph paragraph in paragraphs)
    {
        if (paragraph.ParagraphFormat.Style.Name == styleName)
            paragraphsWithStyle.Add(paragraph);
    }

    return paragraphsWithStyle;
}
private static void VerifyParameterNodes(Node startNode, Node endNode)
{
    // El orden en que se realizan estas comprobaciones es importante.
    if (startNode == null)
        throw new ArgumentException("Start node cannot be null");
    if (endNode == null)
        throw new ArgumentException("End node cannot be null");

    if (!startNode.Document.Equals(endNode.Document))
        throw new ArgumentException("Start node and end node must belong to the same document");

    if (startNode.GetAncestor(NodeType.Body) == null || endNode.GetAncestor(NodeType.Body) == null)
        throw new ArgumentException("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.ParentNode.IndexOf(startSection);
    int endIndex = endSection.ParentNode.IndexOf(endSection);

    if (startIndex == endIndex)
    {
        if (startSection.Body.IndexOf(startNode) > endSection.Body.IndexOf(endNode))
            throw new ArgumentException("The end node must be after the start node in the body");
    }
    else if (startIndex > endIndex)
        throw new ArgumentException("The section of end node must be after the section start node");
}
private static bool IsInline(Node node)
{
    // Pruebe si el nodo es descendiente de un nodo Párrafo o Tabla y tampoco es un párrafo o una tabla. Es posible un párrafo dentro de una clase de comentario que sea descendiente de un párrafo.
    return ((node.GetAncestor(NodeType.Paragraph) != null || node.GetAncestor(NodeType.Table) != null) && !(node.NodeType == NodeType.Paragraph || node.NodeType == NodeType.Table));
}
private static void ProcessMarker(CompositeNode cloneNode, ArrayList nodes, Node node, bool isInclusive, bool isStartMarker, bool isEndMarker)
{
    // Si estamos tratando con un nodo de nivel de bloque, simplemente vea si debe incluirse y agréguelo 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.NodeType == NodeType.FieldStart)
    {
        // Si el marcador es un nodo de inicio y no se incluye, vaya al final del campo.
        // Si el marcador es un nodo final y debe incluirse, muévase al campo final para que no se elimine.
        if ((isStartMarker && !isInclusive) || (!isStartMarker && isInclusive))
        {
            while (node.NextSibling != null && node.NodeType != NodeType.FieldEnd)
                node = node.NextSibling;

        }
    }

    // Si cualquiera de los marcadores es parte de un comentario, para incluir el comentario en sí, debemos mover el puntero hacia el comentario.
    // Nodo encontrado después del nodo CommentRangeEnd.
    if (node.NodeType == NodeType.CommentRangeEnd)
    {
        while (node.NextSibling != null && node.NodeType != NodeType.Comment)
            node = node.NextSibling;

    }

    // 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 ya se hayan eliminado algunos nodos secundarios. Restar el
    // Diferencia para obtener el índice correcto.
    int indexDiff = node.ParentNode.ChildNodes.Count - cloneNode.ChildNodes.Count;

    // Recuento de nodos secundarios idéntico.
    if (indexDiff == 0)
        node = cloneNode.ChildNodes[node.ParentNode.IndexOf(node)];
    else
        node = cloneNode.ChildNodes[node.ParentNode.IndexOf(node) - indexDiff];

    // Retire los nodos hasta/desde el marcador.
    bool isSkip = false;
    bool isProcessing = true;
    bool isRemoving = isStartMarker;
    Node nextNode = cloneNode.FirstChild;

    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.NextSibling;
        if (isRemoving && !isSkip)
            currentNode.Remove();
    }

    // Después del procesamiento, el nodo compuesto puede quedar vacío. Si lo tiene, no lo incluya.
    if (!(isStartMarker && isEndMarker))
    {
        if (cloneNode.HasChildNodes)
            nodes.Add(cloneNode);
    }

}
public static Document GenerateDocument(Document srcDoc, ArrayList nodes)
{
    // Crea un documento en blanco.
    Document dstDoc = new Document();
    // Elimina el primer párrafo del documento vacío.
    dstDoc.FirstSection.Body.RemoveAllChildren();

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

    foreach (Node node in nodes)
    {
        Node importNode = importer.ImportNode(node, true);
        dstDoc.FirstSection.Body.AppendChild(importNode);
    }

    // Devolver el documento generado.
    return dstDoc;
}

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

Extraer texto entre párrafos en un documento 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 C#.

  • 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.FirstSection.Body.GetChild(NodeType.PARAGRAPH, int, boolean).
  • 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 consiste 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 documento de Word en C#.

// Cargar documento de Word
Document doc = new Document("document.docx");

// Reúna los nodos (el método GetChild usa un índice basado en 0)
Paragraph startPara = (Paragraph)doc.FirstSection.Body.GetChild(NodeType.Paragraph, 6, true);
Paragraph endPara = (Paragraph)doc.FirstSection.Body.GetChild(NodeType.Paragraph, 10, true);

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

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

Extraiga texto entre diferentes tipos de nodos en un documento de Word

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 realizar esta operación.

  • 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.FirstSection.Body.GetChild(NodeType, int, boolean).
  • 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 consiste 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 C#.

// Cargar documento de Word
Document doc = new Document("document.docx");

Paragraph startPara = (Paragraph)doc.LastSection.GetChild(NodeType.Paragraph, 2, true);
Table endTable = (Table)doc.LastSection.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);

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

Extraer texto entre párrafos según 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 C#.

  • Primero, cargue el documento de Word usando la clase Document.
  • Luego, extraiga párrafos en un objeto usando el método auxiliar ParagraphsByStyleName(Document, “Heading 1”).
  • Extraiga párrafos en otro objeto usando el método auxiliar ParagraphsByStyleName(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 consiste 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 de Word
Document doc = new Document("document.docx");

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

// Utilice la primera instancia de los párrafos con esos estilos.
Node startPara1 = (Node)parasStyleHeading1[0];
Node endPara1 = (Node)parasStyleHeading3[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 documento nuevo y guárdelo en el disco.
Document dstDoc = GenerateDocument(doc, extractedNodes);
dstDoc.Save("output.docx");

Lee mas

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

Obtenga una licencia de API gratuita

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

Conclusión

En este artículo, ha aprendido a extraer texto de documentos de MS Word usando C#. 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 C#. Además, puede explorar otras características de Aspose.Words for .NET 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

Sugerencia: es posible que desee consultar Aspose PowerPoint to Word Converter porque demuestra el popular proceso de conversión de presentaciones a documentos de Word.