JavaでMSWord文書からテキストを抽出する

Word文書からのテキスト抽出は、多くの場合、さまざまなシナリオで実行されます。たとえば、テキストを分析したり、ドキュメントの特定のセクションを抽出してそれらを1つのドキュメントに結合したりします。この記事では、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。これらは、ブロックレベル(段落、表)またはインラインレベル(実行、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)) {
		// マーカーが同じノードである場合は、ノードを2回追加しないでください
		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ドキュメントの2つの段落の間のコンテンツを抽出する方法を見てみましょう。以下は、Javaでこの操作を実行するための手順です。

  • まず、Documentクラスを使用してWord文書をロードします。
  • Document.getFirstSection().getChild(NodeType.PARAGRAPH、int, bool)メソッドを使用して、開始段落と終了段落の参照を2つのオブジェクトに取得します。
  • extractContent(startPara、endPara、true)メソッドを呼び出して、ノードをオブジェクトに抽出します。
  • generateDocument(Document、extractedNodes)ヘルパーメソッドを呼び出して、抽出されたコンテンツで構成されるドキュメントを作成します。
  • 最後に、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");

JavaはDOCからテキストを抽出します-異なるタイプのノード間

異なるタイプのノード間でコンテンツを抽出することもできます。デモンストレーションのために、段落と表の間のコンテンツを抽出して、新しいWord文書に保存しましょう。以下は、JavaのWord文書内の異なるノード間でテキストを抽出する手順です。

  • Documentクラスを使用してWord文書をロードします。
  • Document.getFirstSection().getChild(NodeType、int, bool)メソッドを使用して、開始ノードと終了ノードの参照を2つのオブジェクトに取得します。
  • extractContent(startPara、endPara、true)メソッドを呼び出して、ノードをオブジェクトに抽出します。
  • generateDocument(Document、extractedNodes)ヘルパーメソッドを呼び出して、抽出されたコンテンツで構成されるドキュメントを作成します。
  • 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)メソッドを呼び出し、両方の段落配列の最初の要素を最初と2番目のパラメーターとして渡します。
  • generateDocument(Document、extractedNodes)ヘルパーメソッドを呼び出して、抽出されたコンテンツで構成されるドキュメントを作成します。
  • 最後に、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");

Java Word Text Extractor-続きを読む

thisドキュメント記事を使用して、Wordドキュメントからテキストを抽出する他のシナリオを調べることができます。

DOC/DOCXからテキストを抽出するJava API-無料ライセンスを取得

評価の制限なしにAspose.Words for Javaを使用するための一時ライセンスを取得できます。

結論

この記事では、JavaでMS Word DOC DOCXからテキストを抽出する方法を学びました。さらに、Word文書内の類似または異なるタイプのノード間でプログラムによってコンテンツを抽出する方法を見てきました。したがって、Javaで独自のMS Wordテキスト抽出機能を構築できます。さらに、ドキュメントを使用して、Aspose.Words for Javaの他の機能を調べることができます。ご不明な点がございましたら、フォーラムからお気軽にお問い合わせください。

関連項目