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

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 lập trình trong Java. 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 Java để trích xuất văn bản từ tài liệu Word

Aspose.Words dành cho Java 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 JAR của API hoặc cài đặt nó bằng cách sử dụng các cấu hình Maven sau.

<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>

Trích xuất văn bản trong Word DOC / DOCX trong Java

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ể thay đổi tùy theo 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 Word DOC / DOCX đượ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ừ một Word DOC trong Java

Trong phần này, chúng ta sẽ triển khai trình trích xuất văn bản Java 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 để 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à cách 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 qua.

// Để có các ví dụ và tệp dữ liệu đầy đủ, vui lòng truy cập https://github.com/aspose-words/Aspose.Words-for-Java
public static ArrayList extractContent(Node startNode, Node endNode, boolean isInclusive) throws Exception {
    // 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.getParentNode().getNodeType() != NodeType.BODY)
        startNode = startNode.getParentNode();

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

    boolean isExtracting = true;
    boolean isStartingNode = true;
    boolean isEndingNode;
    // 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.
        /*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) {
            // 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(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(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.getNextSibling() == null && isExtracting) {
            // Chuyển sang phần tiếp theo.
            Section nextSection = (Section) currNode.getAncestor(NodeType.SECTION).getNextSibling();
            currNode = nextSection.getBody().getFirstChild();
        } else {
            // Di chuyển đến nút tiếp theo trong nội dung.
            currNode = currNode.getNextSibling();
        }
    }

    // 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.

/**
 * Kiểm tra các thông số đầu vào là chính xác và có thể được sử dụng. Ném một ngoại lệ
 * nếu có bất kỳ vấn đề.
 */
private static void verifyParameterNodes(Node startNode, Node endNode) throws Exception {
	// Thứ tự thực hiện các kiểm tra này là quan trọng.
	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");

	// 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 không, sau đó kiểm tra xem chúng có nằm trong các phần khác nhau không
	// vị trí của chúng trong phần thân của cùng một phần mà chúng đang ở.
	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");
}

/**
 * Kiểm tra xem một nút được truyền có phải là một nút nội tuyến hay không.
 */
private static boolean isInline(Node node) throws Exception {
	// Kiểm tra xem nút đó có phải là phần phụ của nút Đoạn hoặc Bảng và cũng không phải là
	// đoạn văn hoặc một bảng một đoạn văn bên trong một lớp nhận xét mà là
	// một pararaph là có thể.
	return ((node.getAncestor(NodeType.PARAGRAPH) != null || node.getAncestor(NodeType.TABLE) != null)
			&& !(node.getNodeType() == NodeType.PARAGRAPH || node.getNodeType() == NodeType.TABLE));
}

/**
 * Xóa nội dung trước hoặc sau điểm đánh dấu trong nút nhân bản tùy thuộc
 * về loại điểm đánh dấu.
 */
private static void processMarker(CompositeNode cloneNode, ArrayList nodes, Node node, boolean isInclusive,
		boolean isStartMarker, boolean isEndMarker) throws Exception {
	// 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 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.
	// Chúng tôi giả định để đơn giản rằng FieldStart và FieldEnd xuất hiện trong cùng một
	// đoạn văn.
	if (node.getNodeType() == NodeType.FIELD_START) {
		// 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
		// cánh đồ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 cuối
		// trường nên trường sẽ không bị xóa.
		if ((isStartMarker && !isInclusive) || (!isStartMarker && isInclusive)) {
			while (node.getNextSibling() != null && node.getNodeType() != NodeType.FIELD_END)
				node = node.getNextSibling();

		}
	}

	// 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 tôi
	// cần di chuyển con trỏ tới Nhận xét
	// được tìm thấy sau nút CommentRangeEnd.
	if (node.getNodeType() == NodeType.COMMENT_RANGE_END) {
		while (node.getNextSibling() != null && node.getNodeType() != NodeType.COMMENT)
			node = node.getNextSibling();

	}

	// 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ể đã có
	// đã bị loại bỏ. Trừ đi
	// khác biệt để có được chỉ số phù hợp.
	int indexDiff = node.getParentNode().getChildNodes().getCount() - cloneNode.getChildNodes().getCount();

	// Số nút con giống hệt nhau.
	if (indexDiff == 0)
		node = cloneNode.getChildNodes().get(node.getParentNode().indexOf(node));
	else
		node = cloneNode.getChildNodes().get(node.getParentNode().indexOf(node) - indexDiff);

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

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

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

	// 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.getFirstSection().getBody().removeAllChildren();

	// Nhập từng nút từ danh sách vào tài liệu mới. Giữ bản gốc
	// định dạng của nút.
	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);
	}

	// 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 Java giữa các đoạn trong Word DOC

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 Java.

  • Đầu tiên, tải tài liệu Word bằng lớp Tài liệu.
  • Lấy 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.getFirstSection(). GetChild (NodeType.PARAGRAPH, int, bool).
  • 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 createDocument (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 các đoạn văn thứ 7 và 11 trong Word DOCX bằng Java.

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

// Tập hợp các nút. Phương thức GetChild sử dụng chỉ mục dựa trên 0
Paragraph startPara = (Paragraph) doc.getFirstSection().getChild(NodeType.PARAGRAPH, 6, true);
Paragraph endPara = (Paragraph) doc.getFirstSection().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 những
// đánh dấu trong phần chiết.
ArrayList extractedNodes = extractContent(startPara, endPara, true);

// Chèn nội dung vào một tài liệu riêng biệt mới và lưu vào đĩa.
Document dstDoc = generateDocument(doc, extractedNodes);
dstDoc.save("output.doc");

Java Trích xuất văn bản từ DOC - Giữa các loại nút khác nhau

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 để trích xuất văn bản giữa các nút khác nhau trong tài liệu Word bằng Java.

  • Tải tài liệu Word bằng lớp Tài liệu.
  • Lấy 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.getFirstSection(). GetChild (NodeType, int, bool).
  • 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 createDocument (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 DOCX bằng Java.

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

// Lấy tham chiếu của đoạn bắt đầu
Paragraph startPara = (Paragraph) doc.getLastSection().getChild(NodeType.PARAGRAPH, 2, true);
Table endTable = (Table) doc.getLastSection().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);

// Cho phép đảo ngược mảng để chèn nội dung trở lại tài liệu dễ dàng hơn.
Collections.reverse(extractedNodes);

while (extractedNodes.size() > 0) {
    // Chèn nút cuối cùng từ danh sách đảo ngược
    endTable.getParentNode().insertAfter((Node) extractedNodes.get(0), endTable);
    // Xóa nút này khỏi danh sách sau khi chèn.
    extractedNodes.remove(0);
}

// Lưu tài liệu đã tạo vào đĩa.
doc.save("output.doc");

Java Trích xuất Văn bản từ DOCX - 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 Java.

  • Đầ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 cách sử dụ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 cách sử dụ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 createDocument (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
Document doc = new Document(dataDir + "TestFile.doc");

// 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.
ArrayList parasStyleHeading1 = paragraphsByStyleName(doc, "Heading 1");
ArrayList 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.get(0);
Node endPara1 = (Node) parasStyleHeading3.get(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 riêng biệt mới và lưu vào đĩa.
Document dstDoc = generateDocument(doc, extractedNodes);
dstDoc.save("output.doc");

Java Word Text Extractor - Đọ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.

Java API để trích xuất văn bản từ DOC / DOCX - Nhận giấy phép miễn phí

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

Sự kết luận

Trong bài này, bạn đã học cách trích xuất văn bản từ MS Word DOC DOCX bằng Java. 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 bằng Java. Ngoài ra, bạn có thể khám phá các tính năng khác của Aspose.Words dành cho Java 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