Trích xuất văn bản từ tài liệu MS Word trong C#

Trích xuất văn bản từ tài liệu Word thường được thực hiện trong các tình huống khác nhau. Ví dụ: để phân tích văn bản, trích xuất các phần cụ thể của tài liệu và kết hợp chúng thành một tài liệu duy nhất, v.v. Trong bài viết này, bạn sẽ học cách trích xuất văn bản từ tài liệu Word theo chương trình trong C#. Hơn nữa, chúng tôi sẽ đề cập đến cách trích xuất nội dung giữa các phần tử cụ thể như đoạn văn, bảng, v.v. một cách động.

Thư viện C# để trích xuất văn bản từ tài liệu Word

Aspose.Words for .NET là một thư viện mạnh mẽ cho phép bạn tạo các tài liệu MS Word từ đầu. Hơn nữa, nó cho phép bạn thao tác các tài liệu Word hiện có để mã hóa, chuyển đổi, trích xuất văn bản, v.v. Chúng tôi sẽ sử dụng thư viện này để trích xuất văn bản từ các tài liệu Word DOCX hoặc DOC. Bạn có thể tải xuống DLL của API hoặc cài đặt trực tiếp từ NuGet bằng bảng điều khiển trình quản lý gói.

PM> Install-Package Aspose.Words

Trích xuất văn bản trong tài liệu Word bằng C#

Một tài liệu MS Word bao gồm các phần tử khác nhau bao gồm đoạn văn, bảng, hình ảnh, v.v. Do đó, các yêu cầu trích xuất văn bản có thể khác nhau giữa các tình huống. Ví dụ: bạn có thể cần trích xuất văn bản giữa các đoạn văn, dấu trang, nhận xét, v.v.

Mỗi loại phần tử trong tài liệu Word được biểu diễn dưới dạng một nút. Do đó, để xử lý một tài liệu, bạn sẽ phải chơi với các nút. Vì vậy, hãy bắt đầu và xem cách trích xuất văn bản từ tài liệu Word trong các trường hợp khác nhau.

Trích xuất văn bản từ tài liệu Word trong C#

Trong phần này, chúng ta sẽ triển khai trình trích xuất văn bản C# cho các tài liệu Word và quy trình trích xuất văn bản sẽ như sau:

  • Đầu tiên, chúng tôi sẽ xác định các nút mà chúng tôi muốn đưa vào quá trình trích xuất văn bản.
  • Sau đó, chúng tôi sẽ trích xuất nội dung giữa các nút được chỉ định (bao gồm hoặc loại trừ các nút bắt đầu và kết thúc).
  • Cuối cùng, chúng tôi sẽ sử dụng bản sao của các nút được trích xuất, ví dụ: để tạo một tài liệu Word mới bao gồm nội dung được trích xuất.

Bây giờ chúng ta hãy viết một phương thức có tên là ExtractContent mà chúng ta sẽ truyền các nút và một số tham số khác để thực hiện trích xuất văn bản. Phương thức này sẽ phân tích cú pháp tài liệu và sao chép các nút. Sau đây là các tham số mà chúng ta sẽ truyền cho phương thức này.

  1. StartNode và EndNode lần lượt là điểm bắt đầu và điểm kết thúc cho việc trích xuất nội dung. Đây có thể là cả cấp khối (Đoạn, Bảng) hoặc cấp nội tuyến (ví dụ: Run, FieldStart, BookmarkStart, v.v.).
    1. Để chuyển một trường, bạn nên chuyển đối tượng FieldStart tương ứng.
    2. Để vượt qua dấu trang, các nút BookmarkStart và BookmarkEnd phải được chuyển.
    3. Đối với các nhận xét, các nút CommentRangeStart và CommentRangeEnd nên được sử dụng.
  2. IsInclusive xác định xem các điểm đánh dấu có được đưa vào phần trích xuất hay không. Nếu tùy chọn này được đặt thành false và cùng một nút hoặc các nút liên tiếp được chuyển, thì một danh sách trống sẽ được trả về.

Sau đây là phần triển khai hoàn chỉnh của phương thức ExtractContent trích xuất nội dung giữa các nút được truyền.

public static ArrayList ExtractContent(Node startNode, Node endNode, bool isInclusive)
{
    // Trước tiên, hãy kiểm tra xem các nút được chuyển đến phương thức này có hợp lệ để sử dụng hay không.
    VerifyParameterNodes(startNode, endNode);

    // Tạo một danh sách để lưu trữ các nút đã trích xuất.
    ArrayList nodes = new ArrayList();

    // Giữ bản ghi của các nút ban đầu được chuyển đến phương thức này để chúng tôi có thể chia các nút đánh dấu nếu cần.
    Node originalStartNode = startNode;
    Node originalEndNode = endNode;

    // Trích xuất nội dung dựa trên các nút cấp khối (đoạn văn và bảng). Đi qua các nút cha để tìm chúng.
    // Chúng tôi sẽ chia nội dung của các nút đầu tiên và cuối cùng tùy thuộc vào nếu các nút đánh dấu là nội tuyến
    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;
    // Nút hiện tại mà chúng tôi đang trích xuất từ tài liệu.
    Node currNode = startNode;

    // Bắt đầu trích xuất nội dung. Xử lý tất cả các nút mức khối và tách riêng nút đầu tiên và nút cuối cùng khi cần thiết để định dạng đoạn văn được giữ lại.
    // Phương pháp phức tạp hơn một chút so với một trình giải nén thông thường vì chúng ta cần giải nén bằng cách sử dụng các nút nội tuyến, trường, dấu trang, v.v. để làm cho nó thực sự hữu ích.
    while (isExtracting)
    {
        // Sao chép nút hiện tại và nút con của nó để lấy một bản sao.
        Node cloneNode = currNode.Clone(true);
        isEndingNode = currNode.Equals(endNode);

        if ((isStartingNode || isEndingNode) && cloneNode.IsComposite)
        {
            // Chúng tôi cần xử lý từng điểm đánh dấu riêng biệt, vì vậy thay vào đó, hãy chuyển nó sang một phương pháp riêng biệt.
            if (isStartingNode)
            {
                ProcessMarker((CompositeNode)cloneNode, nodes, originalStartNode, isInclusive, isStartingNode, isEndingNode);
                isStartingNode = false;
            }

            // Điều kiện cần phải được tách biệt vì các điểm đánh dấu bắt đầu và kết thúc cấp khối có thể là cùng một nút.
            if (isEndingNode)
            {
                ProcessMarker((CompositeNode)cloneNode, nodes, originalEndNode, isInclusive, isStartingNode, isEndingNode);
                isExtracting = false;
            }
        }
        else
            // Node không phải là điểm đánh dấu bắt đầu hoặc kết thúc, chỉ cần thêm bản sao vào danh sách.
            nodes.Add(cloneNode);

        // Di chuyển đến nút tiếp theo và giải nén nó. Nếu nút tiếp theo là rỗng có nghĩa là phần còn lại của nội dung được tìm thấy trong một phần khác.
        if (currNode.NextSibling == null && isExtracting)
        {
            // Chuyển sang phần tiếp theo.
            Section nextSection = (Section)currNode.GetAncestor(NodeType.Section).NextSibling;
            currNode = nextSection.Body.FirstChild;
        }
        else
        {
            // Di chuyển đến nút tiếp theo trong nội dung.
            currNode = currNode.NextSibling;
        }
    }

    // Trả lại các nút giữa các điểm đánh dấu nút.
    return nodes;
}

Một số phương thức trợ giúp cũng được yêu cầu bởi phương thức ExtractContent để thực hiện thao tác trích xuất văn bản, được đưa ra bên dưới.

public static List<Paragraph> ParagraphsByStyleName(Document doc, string styleName)
{
    // Tạo một mảng để thu thập các đoạn văn có kiểu được chỉ định.
    List<Paragraph> paragraphsWithStyle = new List<Paragraph>();

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

    // Xem qua tất cả các đoạn văn để tìm những đoạn văn có kiểu được chỉ định.
    foreach (Paragraph paragraph in paragraphs)
    {
        if (paragraph.ParagraphFormat.Style.Name == styleName)
            paragraphsWithStyle.Add(paragraph);
    }

    return paragraphsWithStyle;
}
private static void VerifyParameterNodes(Node startNode, Node endNode)
{
    // Thứ tự thực hiện các kiểm tra này là quan trọng.
    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");

    // Kiểm tra xem nút kết thúc nằm sau nút bắt đầu trong cây DOM
    // Trước tiên, hãy kiểm tra xem chúng có nằm trong các phần khác nhau hay không, sau đó kiểm tra vị trí của chúng trong phần nội dung của cùng một phần mà chúng ở trong đó hay không.
    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)
{
    // Kiểm tra xem nút đó có phải là phần phụ của một Đoạn hoặc nút Bảng và cũng không phải là một đoạn hoặc một bảng hay không, một đoạn bên trong một lớp chú thích là yếu tố của một pararaph có thể thực hiện được hay không.
    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)
{
    // Nếu chúng ta đang xử lý một nút cấp khối, chỉ cần xem liệu nó có nên được đưa vào hay không và thêm nó vào danh sách.
    if (!IsInline(node))
    {
        // Không thêm nút hai lần nếu các điểm đánh dấu là cùng một nút
        if (!(isStartMarker && isEndMarker))
        {
            if (isInclusive)
                nodes.Add(cloneNode);
        }
        return;
    }

    // Nếu một điểm đánh dấu là một nút FieldStart, hãy kiểm tra xem nó có được đưa vào hay không.
    // Để đơn giản, chúng tôi giả định rằng FieldStart và FieldEnd xuất hiện trong cùng một đoạn.
    if (node.NodeType == NodeType.FieldStart)
    {
        // Nếu điểm đánh dấu là nút bắt đầu và không được bao gồm thì hãy bỏ qua đến cuối trường.
        // Nếu điểm đánh dấu là một nút kết thúc và nó sẽ được bao gồm thì hãy di chuyển đến trường kết thúc để trường sẽ không bị xóa.
        if ((isStartMarker && !isInclusive) || (!isStartMarker && isInclusive))
        {
            while (node.NextSibling != null && node.NodeType != NodeType.FieldEnd)
                node = node.NextSibling;

        }
    }

    // Nếu một trong hai điểm đánh dấu là một phần của nhận xét thì để bao gồm chính nhận xét đó, chúng ta cần di chuyển con trỏ tới Nhận xét
    // Nút được tìm thấy sau nút CommentRangeEnd.
    if (node.NodeType == NodeType.CommentRangeEnd)
    {
        while (node.NextSibling != null && node.NodeType != NodeType.Comment)
            node = node.NextSibling;

    }

    // Tìm nút tương ứng trong nút nhân bản của chúng tôi theo chỉ mục và trả lại nó.
    // Nếu nút bắt đầu và nút kết thúc giống nhau, một số nút con có thể đã bị xóa. Trừ đi
    // Sự khác biệt để có được chỉ số phù hợp.
    int indexDiff = node.ParentNode.ChildNodes.Count - cloneNode.ChildNodes.Count;

    // Số nút con giống hệt nhau.
    if (indexDiff == 0)
        node = cloneNode.ChildNodes[node.ParentNode.IndexOf(node)];
    else
        node = cloneNode.ChildNodes[node.ParentNode.IndexOf(node) - indexDiff];

    // Xóa các nút lên đến / khỏi điểm đánh dấu.
    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();
    }

    // Sau khi xử lý, nút tổng hợp có thể trở nên trống. Nếu nó đã không bao gồm nó.
    if (!(isStartMarker && isEndMarker))
    {
        if (cloneNode.HasChildNodes)
            nodes.Add(cloneNode);
    }

}
public static Document GenerateDocument(Document srcDoc, ArrayList nodes)
{
    // Tạo một tài liệu trống.
    Document dstDoc = new Document();
    // Xóa đoạn đầu tiên khỏi tài liệu trống.
    dstDoc.FirstSection.Body.RemoveAllChildren();

    // Nhập từng nút từ danh sách vào tài liệu mới. Giữ nguyên định dạng ban đầu của nút.
    NodeImporter importer = new NodeImporter(srcDoc, dstDoc, ImportFormatMode.KeepSourceFormatting);

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

    // Trả lại tài liệu đã tạo.
    return dstDoc;
}

Bây giờ chúng tôi đã sẵn sàng sử dụng các phương pháp này và trích xuất văn bản từ tài liệu Word.

Trích xuất văn bản giữa các đoạn trong tài liệu Word

Hãy xem cách trích xuất nội dung giữa hai đoạn văn trong tài liệu Word DOCX. Sau đây là các bước để thực hiện thao tác này trong C#.

  • Đầu tiên, tải tài liệu Word bằng lớp Tài liệu.
  • Nhận tham chiếu của các đoạn văn bắt đầu và kết thúc thành hai đối tượng bằng cách sử dụng phương thức Document.FirstSection.Body.GetChild (NodeType.PARAGRAPH, int, boolean).
  • Gọi phương thức ExtractContent (startPara, endPara, True) để trích xuất các nút thành một đối tượng.
  • Gọi phương thức trợ giúp GenerateDocument (Document, ExtractNodes) để tạo tài liệu bao gồm nội dung được trích xuất.
  • Cuối cùng, lưu tài liệu trả về bằng phương thức Document.Save (string).

Mẫu mã sau đây cho thấy cách trích xuất văn bản giữa đoạn văn thứ 7 và thứ 11 trong tài liệu Word bằng C#.

// Tải tài liệu Word
Document doc = new Document("document.docx");

// Thu thập các nút (phương pháp GetChild sử dụng chỉ mục dựa trên 0)
Paragraph startPara = (Paragraph)doc.FirstSection.Body.GetChild(NodeType.Paragraph, 6, true);
Paragraph endPara = (Paragraph)doc.FirstSection.Body.GetChild(NodeType.Paragraph, 10, true);

// Trích xuất nội dung giữa các nút này trong tài liệu. Bao gồm các điểm đánh dấu này trong phần trích xuất.
ArrayList extractedNodes = ExtractContent(startPara, endPara, true);

// Chèn nội dung vào một tài liệu mới và lưu nó vào đĩa.
Document dstDoc = GenerateDocument(doc, extractedNodes);
dstDoc.Save("output.docx");

Trích xuất văn bản giữa các loại nút khác nhau trong tài liệu Word

Bạn cũng có thể trích xuất nội dung giữa các loại nút khác nhau. Để minh họa, hãy trích xuất nội dung giữa một đoạn văn và một bảng và lưu nó vào một tài liệu Word mới. Sau đây là các bước để thực hiện thao tác này.

  • Tải tài liệu Word bằng lớp Tài liệu.
  • Nhận tham chiếu của các nút bắt đầu và kết thúc thành hai đối tượng bằng cách sử dụng phương thức Document.FirstSection.Body.GetChild (NodeType, int, boolean).
  • Gọi phương thức ExtractContent (startPara, endPara, True) để trích xuất các nút thành một đối tượng.
  • Gọi phương thức trợ giúp GenerateDocument (Document, ExtractNodes) để tạo tài liệu bao gồm nội dung được trích xuất.
  • Lưu tài liệu trả về bằng phương thức Document.Save (string).

Mẫu mã sau đây cho thấy cách trích xuất văn bản giữa một đoạn văn và một bảng trong C#.

// Tải tài liệu 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);

// Trích xuất nội dung giữa các nút này trong tài liệu. Bao gồm các điểm đánh dấu này trong phần trích xuất.
ArrayList extractedNodes = ExtractContent(startPara, endTable, true);

// Chèn nội dung vào một tài liệu mới và lưu nó vào đĩa.
Document dstDoc = GenerateDocument(doc, extractedNodes);
dstDoc.Save("output.docx");

Trích xuất Văn bản giữa các Đoạn văn dựa trên Kiểu

Bây giờ chúng ta hãy kiểm tra cách trích xuất nội dung giữa các đoạn văn dựa trên phong cách. Để minh họa, chúng tôi sẽ trích xuất nội dung giữa “Tiêu đề 1” đầu tiên và “Tiêu đề 3” đầu tiên trong tài liệu Word. Các bước sau đây trình bày cách đạt được điều này trong C#.

  • Đầu tiên, tải tài liệu Word bằng lớp Tài liệu.
  • Sau đó, trích xuất các đoạn văn vào một đối tượng bằng phương thức trợ giúp ParagraphsByStyleName (Document, “Heading 1”).
  • Trích xuất các đoạn văn vào một đối tượng khác bằng phương thức trợ giúp ParagraphsByStyleName (Document, “Heading 3”).
  • Gọi phương thức ExtractContent (startPara, endPara, True) và chuyển các phần tử đầu tiên trong cả hai mảng đoạn làm tham số đầu tiên và thứ hai.
  • Gọi phương thức trợ giúp GenerateDocument (Document, ExtractNodes) để tạo tài liệu bao gồm nội dung được trích xuất.
  • Cuối cùng, lưu tài liệu trả về bằng phương thức Document.Save (string).

Mẫu mã sau đây cho thấy cách trích xuất nội dung giữa các đoạn văn dựa trên kiểu.

// Tải tài liệu Word
Document doc = new Document("document.docx");

// Tập hợp danh sách các đoạn văn bằng cách sử dụng các kiểu tiêu đề tương ứng.
List<Paragraph> parasStyleHeading1 = ParagraphsByStyleName(doc, "Heading 1");
List<Paragraph> parasStyleHeading3 = ParagraphsByStyleName(doc, "Heading 3");

// Sử dụng phiên bản đầu tiên của các đoạn văn với các phong cách đó.
Node startPara1 = (Node)parasStyleHeading1[0];
Node endPara1 = (Node)parasStyleHeading3[0];

// Trích xuất nội dung giữa các nút này trong tài liệu. Không bao gồm các điểm đánh dấu này trong phần trích xuất.
ArrayList extractedNodes = ExtractContent(startPara1, endPara1, false);

// Chèn nội dung vào một tài liệu mới và lưu nó vào đĩa.
Document dstDoc = GenerateDocument(doc, extractedNodes);
dstDoc.Save("output.docx");

Đọc thêm

Bạn có thể khám phá các trường hợp trích xuất văn bản từ tài liệu Word khác bằng cách sử dụng bài viết tài liệu này.

Nhận giấy phép API miễn phí

Bạn có thể nhận giấy phép tạm thời để sử dụng Aspose.Words for .NET mà không có giới hạn đánh giá.

Sự kết luận

Trong bài này, bạn đã biết cách trích xuất văn bản từ tài liệu MS Word bằng C#. Hơn nữa, bạn đã thấy cách trích xuất nội dung giữa các loại nút tương tự hoặc khác nhau trong tài liệu Word theo chương trình. Do đó, bạn có thể xây dựng trình trích xuất văn bản MS Word của riêng mình trong C#. Ngoài ra, bạn có thể khám phá các tính năng khác của Aspose. AdWords for .NET bằng cách sử dụng tài liệu. Trong trường hợp bạn có bất kỳ câu hỏi nào, vui lòng cho chúng tôi biết qua diễn đàn của chúng tôi.

Xem thêm

Mẹo: Bạn có thể muốn kiểm tra Bộ chuyển đổi Aspose PowerPoint to Word vì nó thể hiện quá trình chuyển đổi bản trình bày phổ biến sang tài liệu Word.