Wyodrębnij tekst z dokumentów MS Word w C#

Wyodrębnianie tekstu z dokumentów programu Word jest często wykonywane w różnych scenariuszach. Na przykład, aby przeanalizować tekst, wyodrębnić określone sekcje dokumentu i połączyć je w jeden dokument i tak dalej. W tym artykule dowiesz się, jak programowo wyodrębnić tekst z dokumentów programu Word w języku C#. Ponadto omówimy, jak dynamicznie wyodrębniać zawartość między określonymi elementami, takimi jak akapity, tabele itp.

Biblioteka C# do wyodrębniania tekstu z dokumentów programu Word

Aspose.Words for .NET to potężna biblioteka, która pozwala tworzyć od podstaw dokumenty MS Word. Ponadto pozwala manipulować istniejącymi dokumentami Word w celu szyfrowania, konwersji, ekstrakcji tekstu itp. Użyjemy tej biblioteki do wyodrębnienia tekstu z dokumentów Word DOCX lub DOC. Możesz pobrać bibliotekę DLL API lub zainstalować ją bezpośrednio z NuGet przy użyciu konsoli menedżera pakietów.

PM> Install-Package Aspose.Words

Ekstrakcja tekstu w dokumentach programu Word przy użyciu języka C#

Dokument MS Word składa się z różnych elementów, takich jak akapity, tabele, obrazy itp. Dlatego wymagania dotyczące ekstrakcji tekstu mogą się różnić w zależności od scenariusza. Na przykład może być konieczne wyodrębnienie tekstu między akapitami, zakładkami, komentarzami itp.

Każdy typ elementu w dokumencie programu Word jest reprezentowany jako węzeł. Dlatego, aby przetworzyć dokument, będziesz musiał bawić się węzłami. Zacznijmy więc i zobaczmy, jak wyodrębnić tekst z dokumentów programu Word w różnych scenariuszach.

Wyodrębnij tekst z dokumentu programu Word w języku C#

W tej sekcji zamierzamy zaimplementować ekstraktor tekstu w języku C# dla dokumentów programu Word, a przepływ pracy podczas wyodrębniania tekstu będzie wyglądał następująco:

  • Najpierw zdefiniujemy węzły, które chcemy uwzględnić w procesie ekstrakcji tekstu.
  • Następnie wyodrębnimy zawartość między określonymi węzłami (w tym lub z wyłączeniem węzłów początkowych i końcowych).
  • Na koniec wykorzystamy klon wyodrębnionych węzłów np. do stworzenia nowego dokumentu Worda składającego się z wyodrębnionej treści.

Napiszmy teraz metodę o nazwie ExtractContent, do której przekażemy węzły i kilka innych parametrów w celu przeprowadzenia ekstrakcji tekstu. Ta metoda przeanalizuje dokument i sklonuje węzły. Poniżej przedstawiono parametry, które przekażemy tej metodzie.

  1. StartNode i EndNode odpowiednio jako punkty początkowe i końcowe wyodrębniania zawartości. Mogą to być zarówno węzły na poziomie bloku (Akapit , Tabela), jak i na poziomie wiersza (np. Run, FieldStart, BookmarkStart itp.).
    1. Aby przekazać pole, należy przekazać odpowiedni obiekt FieldStart.
    2. Aby przekazać zakładki, należy przekazać węzły BookmarkStart i BookmarkEnd.
    3. W przypadku komentarzy należy używać węzłów CommentRangeStart i CommentRangeEnd.
  2. IsInclusive określa, czy znaczniki są uwzględniane w ekstrakcji, czy nie. Jeśli ta opcja jest ustawiona na false i zostanie przekazany ten sam węzeł lub kolejne węzły, zwrócona zostanie pusta lista.

Poniżej przedstawiono pełną implementację metody ExtractContent, która wyodrębnia zawartość między przekazywanymi węzłami.

public static ArrayList ExtractContent(Node startNode, Node endNode, bool isInclusive)
{
    // Najpierw sprawdź, czy węzły przekazane do tej metody są prawidłowe do użycia.
    VerifyParameterNodes(startNode, endNode);

    // Utwórz listę do przechowywania wyodrębnionych węzłów.
    ArrayList nodes = new ArrayList();

    // Zachowaj zapis oryginalnych węzłów przekazanych do tej metody, abyśmy mogli w razie potrzeby podzielić węzły znaczników.
    Node originalStartNode = startNode;
    Node originalEndNode = endNode;

    // Wyodrębnij zawartość na podstawie węzłów na poziomie bloku (akapity i tabele). Przechodź przez węzły nadrzędne, aby je znaleźć.
    // Podzielimy zawartość pierwszego i ostatniego węzła w zależności od tego, czy węzły znaczników są wbudowane
    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;
    // Bieżący węzeł, który wyodrębniamy z dokumentu.
    Node currNode = startNode;

    // Rozpocznij wyodrębnianie zawartości. Przetwarzaj wszystkie węzły na poziomie bloku iw razie potrzeby rozdzielaj pierwszy i ostatni węzły, aby zachować formatowanie akapitów.
    // Metoda jest nieco bardziej złożona niż zwykły ekstraktor, ponieważ musimy wziąć pod uwagę wyodrębnianie za pomocą wbudowanych węzłów, pól, zakładek itp., Aby była naprawdę użyteczna.
    while (isExtracting)
    {
        // Sklonuj bieżący węzeł i jego elementy podrzędne, aby uzyskać kopię.
        Node cloneNode = currNode.Clone(true);
        isEndingNode = currNode.Equals(endNode);

        if ((isStartingNode || isEndingNode) && cloneNode.IsComposite)
        {
            // Musimy przetworzyć każdy znacznik osobno, więc zamiast tego przekaż go osobnej metodzie.
            if (isStartingNode)
            {
                ProcessMarker((CompositeNode)cloneNode, nodes, originalStartNode, isInclusive, isStartingNode, isEndingNode);
                isStartingNode = false;
            }

            // Warunek musi być oddzielny, ponieważ znaczniki początku i końca na poziomie bloku mogą być tym samym węzłem.
            if (isEndingNode)
            {
                ProcessMarker((CompositeNode)cloneNode, nodes, originalEndNode, isInclusive, isStartingNode, isEndingNode);
                isExtracting = false;
            }
        }
        else
            // Węzeł nie jest znacznikiem początkowym ani końcowym, po prostu dodaj kopię do listy.
            nodes.Add(cloneNode);

        // Przejdź do następnego węzła i wyodrębnij go. Jeśli następny węzeł ma wartość null, oznacza to, że reszta treści znajduje się w innej sekcji.
        if (currNode.NextSibling == null && isExtracting)
        {
            // Przejdź do następnej sekcji.
            Section nextSection = (Section)currNode.GetAncestor(NodeType.Section).NextSibling;
            currNode = nextSection.Body.FirstChild;
        }
        else
        {
            // Przejdź do następnego węzła w ciele.
            currNode = currNode.NextSibling;
        }
    }

    // Zwróć węzły między znacznikami węzłów.
    return nodes;
}

Niektóre metody pomocnicze są również wymagane przez metodę ExtractContent do wykonania operacji wyodrębniania tekstu, które podano poniżej.

public static List<Paragraph> ParagraphsByStyleName(Document doc, string styleName)
{
    // Utwórz tablicę, aby zebrać akapity o określonym stylu.
    List<Paragraph> paragraphsWithStyle = new List<Paragraph>();

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

    // Przejrzyj wszystkie akapity, aby znaleźć te, które mają określony styl.
    foreach (Paragraph paragraph in paragraphs)
    {
        if (paragraph.ParagraphFormat.Style.Name == styleName)
            paragraphsWithStyle.Add(paragraph);
    }

    return paragraphsWithStyle;
}
private static void VerifyParameterNodes(Node startNode, Node endNode)
{
    // Kolejność przeprowadzania tych kontroli jest ważna.
    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");

    // Sprawdź, czy węzeł końcowy znajduje się za węzłem początkowym w drzewie DOM
    // Najpierw sprawdź, czy znajdują się w różnych sekcjach, a jeśli nie, sprawdź ich pozycję w treści tej samej sekcji, w której się znajdują.
    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)
{
    // Możliwe jest sprawdzenie, czy węzeł jest potomkiem węzła akapitu lub tabeli, a także nie jest akapitem ani tabelą.
    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)
{
    // Jeśli mamy do czynienia z węzłem na poziomie bloku, po prostu sprawdź, czy powinien zostać uwzględniony i dodaj go do listy.
    if (!IsInline(node))
    {
        // Nie dodawaj węzła dwa razy, jeśli znaczniki są tym samym węzłem
        if (!(isStartMarker && isEndMarker))
        {
            if (isInclusive)
                nodes.Add(cloneNode);
        }
        return;
    }

    // Jeśli znacznik jest węzłem FieldStart, sprawdź, czy ma być uwzględniony, czy nie.
    // Dla uproszczenia zakładamy, że FieldStart i FieldEnd pojawiają się w tym samym akapicie.
    if (node.NodeType == NodeType.FieldStart)
    {
        // Jeśli znacznik jest węzłem początkowym i nie jest uwzględniony, przejdź do końca pola.
        // Jeśli znacznik jest węzłem końcowym i ma być uwzględniony, przesuń się do pola końcowego, aby pole nie zostało usunięte.
        if ((isStartMarker && !isInclusive) || (!isStartMarker && isInclusive))
        {
            while (node.NextSibling != null && node.NodeType != NodeType.FieldEnd)
                node = node.NextSibling;

        }
    }

    // Jeśli któryś ze znaczników jest częścią komentarza, to aby dołączyć sam komentarz, musimy przesunąć wskaźnik do przodu do komentarza
    // Znaleziono węzeł za węzłem CommentRangeEnd.
    if (node.NodeType == NodeType.CommentRangeEnd)
    {
        while (node.NextSibling != null && node.NodeType != NodeType.Comment)
            node = node.NextSibling;

    }

    // Znajdź odpowiedni węzeł w naszym sklonowanym węźle według indeksu i zwróć go.
    // Jeśli węzeł początkowy i końcowy są takie same, niektóre węzły podrzędne mogły już zostać usunięte. Odejmij
    // Różnica, aby uzyskać właściwy indeks.
    int indexDiff = node.ParentNode.ChildNodes.Count - cloneNode.ChildNodes.Count;

    // Liczba węzłów podrzędnych identyczna.
    if (indexDiff == 0)
        node = cloneNode.ChildNodes[node.ParentNode.IndexOf(node)];
    else
        node = cloneNode.ChildNodes[node.ParentNode.IndexOf(node) - indexDiff];

    // Usuń węzły do/od znacznika.
    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();
    }

    // Po przetworzeniu węzeł złożony może stać się pusty. Jeśli ma, nie dołączaj go.
    if (!(isStartMarker && isEndMarker))
    {
        if (cloneNode.HasChildNodes)
            nodes.Add(cloneNode);
    }

}
public static Document GenerateDocument(Document srcDoc, ArrayList nodes)
{
    // Utwórz pusty dokument.
    Document dstDoc = new Document();
    // Usuń pierwszy akapit z pustego dokumentu.
    dstDoc.FirstSection.Body.RemoveAllChildren();

    // Zaimportuj każdy węzeł z listy do nowego dokumentu. Zachowaj oryginalne formatowanie węzła.
    NodeImporter importer = new NodeImporter(srcDoc, dstDoc, ImportFormatMode.KeepSourceFormatting);

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

    // Zwróć wygenerowany dokument.
    return dstDoc;
}

Teraz jesteśmy gotowi do wykorzystania tych metod i wyodrębnienia tekstu z dokumentu Word.

Wyodrębnij tekst między akapitami w dokumencie programu Word

Zobaczmy, jak wyodrębnić zawartość między dwoma akapitami w dokumencie Word DOCX. Poniżej przedstawiono kroki, aby wykonać tę operację w języku C#.

  • Najpierw załaduj dokument programu Word przy użyciu klasy Document.
  • Uzyskaj odwołanie do akapitu początkowego i końcowego do dwóch obiektów za pomocą metody Document.FirstSection.Body.GetChild(NodeType.PARAGRAPH, int, boolean).
  • Wywołaj metodę ExtractContent(startPara, endPara, True), aby wyodrębnić węzły do obiektu.
  • Wywołaj metodę pomocnika GenerateDocument(Document, extractedNodes), aby utworzyć dokument składający się z wyodrębnionej zawartości.
  • Na koniec zapisz zwrócony dokument przy użyciu metody Document.Save(string).

Poniższy przykładowy kod pokazuje, jak wyodrębnić tekst między akapitami 7 i 11 w dokumencie programu Word w języku C#.

// Załaduj dokument Worda
Document doc = new Document("document.docx");

// Zbierz węzły (metoda GetChild używa indeksu opartego na 0)
Paragraph startPara = (Paragraph)doc.FirstSection.Body.GetChild(NodeType.Paragraph, 6, true);
Paragraph endPara = (Paragraph)doc.FirstSection.Body.GetChild(NodeType.Paragraph, 10, true);

// Wyodrębnij zawartość między tymi węzłami w dokumencie. Uwzględnij te znaczniki w ekstrakcji.
ArrayList extractedNodes = ExtractContent(startPara, endPara, true);

// Wstaw zawartość do nowego dokumentu i zapisz ją na dysku.
Document dstDoc = GenerateDocument(doc, extractedNodes);
dstDoc.Save("output.docx");

Wyodrębnij tekst między różnymi typami węzłów w dokumencie programu Word

Możesz także wyodrębniać zawartość między różnymi typami węzłów. Dla demonstracji wyodrębnijmy zawartość między akapitem a tabelą i zapiszmy ją w nowym dokumencie programu Word. Poniżej przedstawiono kroki, aby wykonać tę operację.

  • Załaduj dokument programu Word przy użyciu klasy Document.
  • Uzyskaj odwołanie do węzła początkowego i końcowego do dwóch obiektów za pomocą metody Document.FirstSection.Body.GetChild(NodeType, int, boolean).
  • Wywołaj metodę ExtractContent(startPara, endPara, True), aby wyodrębnić węzły do obiektu.
  • Wywołaj metodę pomocnika GenerateDocument(Document, extractedNodes), aby utworzyć dokument składający się z wyodrębnionej zawartości.
  • Zapisz zwrócony dokument przy użyciu metody Document.Save(string).

Poniższy przykładowy kod pokazuje, jak wyodrębnić tekst między akapitem a tabelą w języku C#.

// Załaduj dokument Worda
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);

// Wyodrębnij zawartość między tymi węzłami w dokumencie. Uwzględnij te znaczniki w ekstrakcji.
ArrayList extractedNodes = ExtractContent(startPara, endTable, true);

// Wstaw zawartość do nowego dokumentu i zapisz ją na dysku.
Document dstDoc = GenerateDocument(doc, extractedNodes);
dstDoc.Save("output.docx");

Wyodrębnij tekst między akapitami na podstawie stylów

Sprawdźmy teraz, jak wyodrębnić treść między akapitami na podstawie stylów. W celu demonstracji wyodrębnimy zawartość między pierwszym „Nagłówkiem 1” a pierwszym „Nagłówkiem 3” w dokumencie programu Word. Poniższe kroki pokazują, jak to osiągnąć w języku C#.

  • Najpierw załaduj dokument programu Word przy użyciu klasy Document.
  • Następnie wyodrębnij akapity do obiektu przy użyciu metody pomocniczej ParagraphsByStyleName(Document, “Heading 1”).
  • Wyodrębnij akapity do innego obiektu przy użyciu metody pomocniczej ParagraphsByStyleName(Document, “Heading 3”).
  • Wywołaj metodę ExtractContent(startPara, endPara, True) i przekaż pierwsze elementy w obu tablicach akapitów jako pierwszy i drugi parametr.
  • Wywołaj metodę pomocnika GenerateDocument(Document, extractedNodes), aby utworzyć dokument składający się z wyodrębnionej zawartości.
  • Na koniec zapisz zwrócony dokument przy użyciu metody Document.Save(string).

Poniższy przykład kodu pokazuje, jak wyodrębnić zawartość między akapitami na podstawie stylów.

// Załaduj dokument Worda
Document doc = new Document("document.docx");

// Zbierz listę akapitów, używając odpowiednich stylów nagłówków.
List<Paragraph> parasStyleHeading1 = ParagraphsByStyleName(doc, "Heading 1");
List<Paragraph> parasStyleHeading3 = ParagraphsByStyleName(doc, "Heading 3");

// Użyj pierwszego wystąpienia akapitów z tymi stylami.
Node startPara1 = (Node)parasStyleHeading1[0];
Node endPara1 = (Node)parasStyleHeading3[0];

// Wyodrębnij zawartość między tymi węzłami w dokumencie. Nie uwzględniaj tych znaczników w ekstrakcji.
ArrayList extractedNodes = ExtractContent(startPara1, endPara1, false);

// Wstaw zawartość do nowego dokumentu i zapisz ją na dysku.
Document dstDoc = GenerateDocument(doc, extractedNodes);
dstDoc.Save("output.docx");

Czytaj więcej

Możesz zapoznać się z innymi scenariuszami wyodrębniania tekstu z dokumentów programu Word, korzystając z tego artykułu w dokumentacji.

Uzyskaj bezpłatną licencję API

Możesz uzyskać tymczasową licencję na używanie Aspose.Words for .NET bez ograniczeń ewaluacyjnych.

Wniosek

W tym artykule nauczyłeś się, jak wyodrębniać tekst z dokumentów MS Word za pomocą C#. Ponadto widziałeś, jak programowo wyodrębniać zawartość między podobnymi lub różnymi typami węzłów w dokumencie programu Word. W ten sposób możesz zbudować własny ekstraktor tekstu MS Word w języku C#. Poza tym możesz poznać inne funkcje Aspose.Words for .NET, korzystając z dokumentacji. Jeśli masz jakieś pytania, daj nam znać za pośrednictwem naszego forum.

Zobacz też

Wskazówka: możesz sprawdzić Aspose PowerPoint to Word Converter, ponieważ demonstruje on popularny proces konwersji prezentacji do dokumentu Word.