Java의 MS Word 문서에서 텍스트 추출

Word 문서에서 텍스트 추출은 종종 다른 시나리오에서 수행됩니다. 예를 들어 텍스트를 분석하거나 문서의 특정 섹션을 추출하여 단일 문서로 결합하는 등의 작업이 있습니다. 이 기사에서는 Java에서 프로그래밍 방식으로 Word 문서에서 텍스트를 추출하는 방법을 배웁니다. 또한 단락, 표 등과 같은 특정 요소 간의 콘텐츠를 동적으로 추출하는 방법을 다룹니다.

Word 문서에서 텍스트를 추출하는 Java 라이브러리

Aspose.Words for Java는 처음부터 MS Word 문서를 만들 수 있는 강력한 라이브러리입니다. 또한 암호화, 변환, 텍스트 추출 등을 위해 기존 Word 문서를 조작할 수 있습니다. 이 라이브러리를 사용하여 Word DOCX 또는 DOC 문서에서 텍스트를 추출합니다. API의 JAR을 다운로드하거나 다음 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>

Java의 Word DOC/DOCX에서 텍스트 추출

MS Word 문서는 단락, 표, 이미지 등을 포함하는 다양한 요소로 구성됩니다. 따라서 텍스트 추출 요구 사항은 시나리오마다 다를 수 있습니다. 예를 들어 단락, 책갈피, 주석 등 사이에서 텍스트를 추출해야 할 수 있습니다.

Word DOC/DOCX의 각 유형의 요소는 노드로 표시됩니다. 따라서 문서를 처리하려면 노드를 가지고 놀아야 합니다. 다양한 시나리오에서 Word 문서에서 텍스트를 추출하는 방법을 시작해 보겠습니다.

Java의 Word DOC에서 텍스트 추출

이 섹션에서는 Word 문서용 Java 텍스트 추출기를 구현하고 텍스트 추출 워크플로는 다음과 같습니다.

  • 먼저 텍스트 추출 프로세스에 포함할 노드를 정의합니다.
  • 그런 다음 지정된 노드(시작 및 끝 노드 포함 또는 제외) 사이의 콘텐츠를 추출합니다.
  • 마지막으로 추출된 노드의 복제본을 사용하여 추출된 콘텐츠로 구성된 새 Word 문서를 만듭니다.

이제 텍스트 추출을 수행하기 위해 노드와 기타 매개변수를 전달할 extractContent라는 메서드를 작성해 보겠습니다. 이 방법은 문서를 구문 분석하고 노드를 복제합니다. 다음은 이 메서드에 전달할 매개변수입니다.

  1. startNode 및 endNode는 각각 콘텐츠 추출을 위한 시작점과 끝점입니다. 이들은 블록 수준(Paragraph, Table) 또는 인라인 수준(예: Run, FieldStart, BookmarkStart 등) 노드일 수 있습니다.
    1. 필드를 전달하려면 해당 FieldStart 개체를 전달해야 합니다.
    2. 책갈피를 전달하려면 BookmarkStart 및 BookmarkEnd 노드를 전달해야 합니다.
    3. 주석의 경우 CommentRangeStart 및 CommentRangeEnd 노드를 사용해야 합니다.
  2. isInclusive는 마커가 추출에 포함되는지 여부를 정의합니다. 이 옵션이 false로 설정되고 동일한 노드 또는 연속 노드가 전달되면 빈 목록이 반환됩니다.

다음은 전달된 노드 사이의 콘텐츠를 추출하는 extractContent 메서드의 완전한 구현입니다.

// 전체 예제 및 데이터 파일을 보려면 https://github.com/aspose-words/Aspose.Words-for-Java로 이동하십시오.
public static ArrayList extractContent(Node startNode, Node endNode, boolean isInclusive) throws Exception {
    // 먼저 이 메소드에 전달된 노드가 사용하기에 유효한지 확인하십시오.
    verifyParameterNodes(startNode, endNode);

    // 추출된 노드를 저장할 목록을 만듭니다.
    ArrayList nodes = new ArrayList();

    // 필요한 경우 마커 노드를 분할할 수 있도록 이 메서드에 전달된 원래 노드의 기록을 유지합니다.
    Node originalStartNode = startNode;
    Node originalEndNode = endNode;

    // 블록 수준 노드(단락 및 표)를 기반으로 콘텐츠를 추출합니다. 상위 노드를 탐색하여 찾습니다.
    // 마커 노드가 인라인인지 여부에 따라 첫 번째 노드와 마지막 노드의 내용을 분할합니다.
    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;
    // 문서에서 추출하는 현재 노드입니다.
    Node currNode = startNode;

    // 콘텐츠 추출을 시작합니다. 모든 블록 수준 노드를 처리하고 단락 형식이 유지되도록 필요할 때 특히 첫 번째 노드와 마지막 노드를 분할합니다.
    // 메서드는 인라인 노드, 필드, 책갈피 등을 사용하여 추출해야 하므로 일반 추출기보다 약간 복잡합니다.
    while (isExtracting) {
        // 현재 노드와 그 자식을 복제하여 복사본을 얻습니다.
        /*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) {
            // 각 마커를 별도로 처리해야 하므로 대신 별도의 메서드로 전달합니다.
            if (isStartingNode) {
                processMarker(cloneNode, nodes, originalStartNode, isInclusive, isStartingNode, isEndingNode);
                isStartingNode = false;
            }

            // 조건부는 블록 레벨 시작 및 종료 마커가 동일한 노드일 수 있으므로 분리되어야 합니다.
            if (isEndingNode) {
                processMarker(cloneNode, nodes, originalEndNode, isInclusive, isStartingNode, isEndingNode);
                isExtracting = false;
            }
        } else
            // 노드는 시작 또는 끝 마커가 아니므로 목록에 복사본을 추가하기만 하면 됩니다.
            nodes.add(cloneNode);

        // 다음 노드로 이동하여 압축을 풉니다. 다음 노드가 null이면 나머지 콘텐츠가 다른 섹션에 있음을 의미합니다.
        if (currNode.getNextSibling() == null && isExtracting) {
            // 다음 섹션으로 이동합니다.
            Section nextSection = (Section) currNode.getAncestor(NodeType.SECTION).getNextSibling();
            currNode = nextSection.getBody().getFirstChild();
        } else {
            // 본문의 다음 노드로 이동합니다.
            currNode = currNode.getNextSibling();
        }
    }

    // 노드 마커 사이의 노드를 반환합니다.
    return nodes;
}

아래에 나와 있는 텍스트 추출 작업을 수행하기 위해 extractContent 메서드에서 일부 도우미 메서드도 필요합니다.

/**
 * 입력 매개변수가 올바르고 사용할 수 있는지 확인합니다. 예외를 던진다
 * 문제가 있는 경우.
 */
private static void verifyParameterNodes(Node startNode, Node endNode) throws Exception {
	// 이러한 검사가 수행되는 순서가 중요합니다.
	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");

	// 끝 노드가 DOM 트리의 시작 노드 뒤에 있는지 확인하십시오.
	// 먼저 다른 섹션에 있는지 확인한 다음 확인하지 않은 경우
	// 그들이 있는 동일한 섹션의 본문에서 위치.
	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");
}

/**
 * 전달된 노드가 인라인 노드인지 확인합니다.
 */
private static boolean isInline(Node node) throws Exception {
	// 노드가 단락 또는 테이블 노드의 하위 노드인지 테스트합니다.
	// 문단 또는 표
	// 단락이 가능합니다.
	return ((node.getAncestor(NodeType.PARAGRAPH) != null || node.getAncestor(NodeType.TABLE) != null)
			&& !(node.getNodeType() == NodeType.PARAGRAPH || node.getNodeType() == NodeType.TABLE));
}

/**
 * 에 따라 복제된 노드의 마커 앞이나 뒤에 있는 내용을 제거합니다.
 * 마커의 유형에.
 */
private static void processMarker(CompositeNode cloneNode, ArrayList nodes, Node node, boolean isInclusive,
		boolean isStartMarker, boolean isEndMarker) throws Exception {
	// 블록 수준 노드를 다루는 경우 포함되어야 하는지 확인하십시오.
	// 목록에 추가합니다.
	if (!isInline(node)) {
		// 마커가 동일한 노드인 경우 노드를 두 번 추가하지 마십시오.
		if (!(isStartMarker && isEndMarker)) {
			if (isInclusive)
				nodes.add(cloneNode);
		}
		return;
	}

	// 마커가 FieldStart 노드인 경우 포함 여부를 확인합니다.
	// 우리는 단순함을 위해 FieldStart와 FieldEnd가 같은 위치에 나타난다고 가정합니다.
	// 절.
	if (node.getNodeType() == NodeType.FIELD_START) {
		// 마커가 시작 노드이고 포함되지 않은 경우 끝으로 건너뜁니다.
		// 필드.
		// 마커가 끝 노드이고 포함되어야 하는 경우 끝으로 이동합니다.
		// 필드가 제거되지 않도록 합니다.
		if ((isStartMarker && !isInclusive) || (!isStartMarker && isInclusive)) {
			while (node.getNextSibling() != null && node.getNodeType() != NodeType.FIELD_END)
				node = node.getNextSibling();

		}
	}

	// 두 마커 중 하나가 주석의 일부인 경우 주석 자체를 포함하려면
	// 포인터를 주석 앞으로 이동해야 합니다.
	// CommentRangeEnd 노드 뒤에 노드가 있습니다.
	if (node.getNodeType() == NodeType.COMMENT_RANGE_END) {
		while (node.getNextSibling() != null && node.getNodeType() != NodeType.COMMENT)
			node = node.getNextSibling();

	}

	// 인덱스로 복제된 노드에서 해당 노드를 찾아 반환합니다.
	// 시작 노드와 끝 노드가 동일한 경우 일부 자식 노드는 이미
	// 제거되었습니다. 빼다
	// 올바른 인덱스를 얻기 위한 차이입니다.
	int indexDiff = node.getParentNode().getChildNodes().getCount() - cloneNode.getChildNodes().getCount();

	// 자식 노드 수가 동일합니다.
	if (indexDiff == 0)
		node = cloneNode.getChildNodes().get(node.getParentNode().indexOf(node));
	else
		node = cloneNode.getChildNodes().get(node.getParentNode().indexOf(node) - indexDiff);

	// 마커까지/에서 노드를 제거합니다.
	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();
	}

	// 복합 노드를 처리한 후 비어 있을 수 있습니다. 포함하지 않은 경우
	// 그것.
	if (!(isStartMarker && isEndMarker)) {
		if (cloneNode.hasChildNodes())
			nodes.add(cloneNode);
	}
}

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

	// 빈 문서를 만듭니다.
	Document dstDoc = new Document();
	// 빈 문서에서 첫 번째 단락을 제거합니다.
	dstDoc.getFirstSection().getBody().removeAllChildren();

	// 목록의 각 노드를 새 문서로 가져옵니다. 원본을 유지
	// 노드의 형식화.
	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);
	}

	// 생성된 문서를 반환합니다.
	return dstDoc;
}

이제 이러한 방법을 활용하고 Word 문서에서 텍스트를 추출할 준비가 되었습니다.

Word DOC의 단락 사이에 Java 추출 텍스트

Word DOCX 문서에서 두 단락 사이의 내용을 추출하는 방법을 살펴보겠습니다. 다음은 Java에서 이 작업을 수행하는 단계입니다.

  • 먼저 Document 클래스를 사용하여 Word 문서를 로드합니다.
  • Document.getFirstSection().getChild(NodeType.PARAGRAPH, int, bool) 메서드를 사용하여 시작 및 끝 단락의 참조를 두 객체로 가져옵니다.
  • extractContent(startPara, endPara, true) 메서드를 호출하여 노드를 객체로 추출합니다.
  • generateDocument(Document, extractNodes) 도우미 메서드를 호출하여 추출된 내용으로 구성된 문서를 만듭니다.
  • 마지막으로 Document.save(String) 메서드를 사용하여 반환된 문서를 저장합니다.

다음 코드 샘플은 Java의 Word DOCX에서 7번째와 11번째 단락 사이의 텍스트를 추출하는 방법을 보여줍니다.

// 문서 로드
Document doc = new Document("TestFile.doc");

// 노드를 수집합니다. GetChild 메서드는 0부터 시작하는 인덱스를 사용합니다.
Paragraph startPara = (Paragraph) doc.getFirstSection().getChild(NodeType.PARAGRAPH, 6, true);
Paragraph endPara = (Paragraph) doc.getFirstSection().getChild(NodeType.PARAGRAPH, 10, true);
// 문서에서 이러한 노드 사이의 콘텐츠를 추출합니다. 다음을 포함
// 추출의 마커.
ArrayList extractedNodes = extractContent(startPara, endPara, true);

// 콘텐츠를 별도의 새 문서에 삽입하고 디스크에 저장합니다.
Document dstDoc = generateDocument(doc, extractedNodes);
dstDoc.save("output.doc");

DOC에서 Java 추출 텍스트 - 다양한 유형의 노드 간

다른 유형의 노드 간에 콘텐츠를 추출할 수도 있습니다. 데모를 위해 단락과 표 사이의 콘텐츠를 추출하여 새 Word 문서에 저장해 보겠습니다. 다음은 Java에서 Word 문서의 서로 다른 노드 간에 텍스트를 추출하는 단계입니다.

  • Document 클래스를 사용하여 Word 문서를 로드합니다.
  • Document.getFirstSection().getChild(NodeType, int, bool) 메서드를 사용하여 시작 및 끝 노드의 참조를 두 객체로 가져옵니다.
  • extractContent(startPara, endPara, true) 메서드를 호출하여 노드를 객체로 추출합니다.
  • generateDocument(Document, extractNodes) 도우미 메서드를 호출하여 추출된 내용으로 구성된 문서를 만듭니다.
  • Document.save(String) 메서드를 사용하여 반환된 문서를 저장합니다.

다음 코드 샘플은 Java를 사용하여 DOCX에서 단락과 테이블 사이의 텍스트를 추출하는 방법을 보여줍니다.

// 문서 로드
Document doc = new Document("TestFile.doc");

// 시작 단락의 참조 가져오기
Paragraph startPara = (Paragraph) doc.getLastSection().getChild(NodeType.PARAGRAPH, 2, true);
Table endTable = (Table) doc.getLastSection().getChild(NodeType.TABLE, 0, true);

// 문서에서 이러한 노드 사이의 콘텐츠를 추출합니다. 추출에 이러한 마커를 포함합니다.
ArrayList extractedNodes = extractContent(startPara, endTable, true);

// 문서에 내용을 더 쉽게 삽입할 수 있도록 배열을 뒤집을 수 있습니다.
Collections.reverse(extractedNodes);

while (extractedNodes.size() > 0) {
    // 역 목록에서 마지막 노드 삽입
    endTable.getParentNode().insertAfter((Node) extractedNodes.get(0), endTable);
    // 삽입 후 목록에서 이 노드를 제거하십시오.
    extractedNodes.remove(0);
}

// 생성된 문서를 디스크에 저장합니다.
doc.save("output.doc");

Java DOCX에서 텍스트 추출 - 스타일 기반 단락 사이

이제 스타일을 기반으로 단락 사이의 내용을 추출하는 방법을 알아보겠습니다. 데모를 위해 Word 문서의 첫 번째 “제목 1"과 첫 번째 “제목 3” 사이의 내용을 추출합니다. 다음 단계는 Java에서 이를 달성하는 방법을 보여줍니다.

  • 먼저 Document 클래스를 사용하여 Word 문서를 로드합니다.
  • 그런 다음, ParagraphsByStyleName(Document, “Heading 1”) 도우미 메서드를 사용하여 단락을 개체로 추출합니다.
  • ParagraphsByStyleName(Document, “Heading 3”) 도우미 메서드를 사용하여 단락을 다른 객체로 추출합니다.
  • extractContent(startPara, endPara, true) 메서드를 호출하고 두 단락 배열의 첫 번째 요소를 첫 번째 및 두 번째 매개변수로 전달합니다.
  • generateDocument(Document, extractNodes) 도우미 메서드를 호출하여 추출된 내용으로 구성된 문서를 만듭니다.
  • 마지막으로 Document.save(String) 메서드를 사용하여 반환된 문서를 저장합니다.

다음 코드 샘플은 스타일을 기반으로 단락 사이의 콘텐츠를 추출하는 방법을 보여줍니다.

// 문서 로드
Document doc = new Document(dataDir + "TestFile.doc");

// 각 제목 스타일을 사용하여 단락 목록을 수집합니다.
ArrayList parasStyleHeading1 = paragraphsByStyleName(doc, "Heading 1");
ArrayList parasStyleHeading3 = paragraphsByStyleName(doc, "Heading 3");

// 해당 스타일이 있는 단락의 첫 번째 인스턴스를 사용합니다.
Node startPara1 = (Node) parasStyleHeading1.get(0);
Node endPara1 = (Node) parasStyleHeading3.get(0);

// 문서에서 이러한 노드 사이의 콘텐츠를 추출합니다. 추출에 이러한 마커를 포함하지 마십시오.
ArrayList extractedNodes = extractContent(startPara1, endPara1, false);

// 콘텐츠를 별도의 새 문서에 삽입하고 디스크에 저장합니다.
Document dstDoc = generateDocument(doc, extractedNodes);
dstDoc.save("output.doc");

자바 단어 텍스트 추출기 - 자세히 보기

문서 문서를 사용하여 Word 문서에서 텍스트를 추출하는 다른 시나리오를 탐색할 수 있습니다.

DOC/DOCX에서 텍스트를 추출하는 Java API - 무료 라이선스 받기

평가 제한 없이 Java용 Aspose.Words를 사용할 수 있는 임시 라이선스를 얻을 수 있습니다.

결론

이 기사에서는 Java의 MS Word DOC DOCX에서 텍스트를 추출하는 방법을 배웠습니다. 또한 프로그래밍 방식으로 Word 문서에서 유사하거나 다른 유형의 노드 간에 콘텐츠를 추출하는 방법을 살펴보았습니다. 따라서 Java로 자신만의 MS Word 텍스트 추출기를 구축할 수 있습니다. 또한 문서를 사용하여 Aspose.Words for Java의 다른 기능을 탐색할 수 있습니다. 질문이 있는 경우 포럼을 통해 언제든지 알려주십시오.

또한보십시오