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

Word文書からのテキスト抽出は、多くの場合、さまざまなシナリオで実行されます。たとえば、テキストを分析したり、ドキュメントの特定のセクションを抽出してそれらを1つのドキュメントに結合したりします。この記事では、C#でプログラムによってWord文書からテキストを抽出する方法を学習します。さらに、段落や表などの特定の要素間でコンテンツを動的に抽出する方法についても説明します。

Word文書からテキストを抽出するためのC#ライブラリ

Aspose.Words for .NETは、MSWordドキュメントを最初から作成できる強力なライブラリです。さらに、暗号化、変換、テキスト抽出などのために既存のWordドキュメントを操作できます。このライブラリを使用して、WordDOCXまたはDOCドキュメントからテキストを抽出します。 APIのDLLをダウンロードするか、パッケージマネージャーコンソールを使用してNuGetから直接インストールできます。

PM> Install-Package Aspose.Words

C#を使用したWord文書でのテキスト抽出

MS Word文書は、段落、表、画像などを含むさまざまな要素で構成されています。したがって、テキスト抽出の要件は、シナリオごとに異なる可能性があります。たとえば、段落、ブックマーク、コメントなどの間にテキストを抽出する必要がある場合があります。

Word文書の各タイプの要素は、ノードとして表されます。したがって、ドキュメントを処理するには、ノードで遊ぶ必要があります。それでは、さまざまなシナリオでWord文書からテキストを抽出する方法を見てみましょう。

C#でWord文書からテキストを抽出する

このセクションでは、Word文書用のC#テキスト抽出機能を実装します。テキスト抽出のワークフローは次のようになります。

  • まず、テキスト抽出プロセスに含めるノードを定義します。
  • 次に、指定したノード間のコンテンツを抽出します(開始ノードと終了ノードを含むまたは除外します)。
  • 最後に、抽出されたノードのクローンを使用します。たとえば、抽出されたコンテンツで構成される新しいWordドキュメントを作成します。

次に、ExtractContentという名前のメソッドを作成します。このメソッドに、ノードとその他のパラメーターを渡して、テキスト抽出を実行します。このメソッドは、ドキュメントを解析し、ノードのクローンを作成します。以下は、このメソッドに渡すパラメーターです。

  1. コンテンツを抽出するための開始点と終了点として、それぞれStartNodeEndNode。これらは、ブロックレベル(段落、表)またはインラインレベル(RunFieldStartBookmarkStartなど)の両方のノードにすることができます。
    1. フィールドを渡すには、対応するFieldStartオブジェクトを渡す必要があります。
    2. ブックマークを渡すには、BookmarkStartノードとBookmarkEndノードを渡す必要があります。
    3. コメントには、CommentRangeStartノードとCommentRangeEndノードを使用する必要があります。
  2. IsInclusiveは、マーカーが抽出に含まれるかどうかを定義します。このオプションがfalseに設定されていて、同じノードまたは連続するノードが渡された場合、空のリストが返されます。

以下は、渡されたノード間でコンテンツを抽出するExtractContentメソッドの完全な実装です。

public static ArrayList ExtractContent(Node startNode, Node endNode, bool isInclusive)
{
    // まず、このメソッドに渡されたノードが使用可能であることを確認します。
    VerifyParameterNodes(startNode, endNode);

    // 抽出したノードを保存するリストを作成します。
    ArrayList nodes = new ArrayList();

    // このメソッドに渡された元のノードの記録を保持して、必要に応じてマーカーノードを分割できるようにします。
    Node originalStartNode = startNode;
    Node originalEndNode = endNode;

    // ブロックレベルのノード(段落と表)に基づいてコンテンツを抽出します。親ノードをトラバースして見つけます。
    // マーカーノードがインラインであるかどうかに応じて、最初と最後のノードのコンテンツを分割します
    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;
    // ドキュメントから抽出している現在のノード。
    Node currNode = startNode;

    // コンテンツの抽出を開始します。すべてのブロックレベルのノードを処理し、必要に応じて最初と最後のノードを具体的に分割して、段落の書式が保持されるようにします。
    // インラインノード、フィールド、ブックマークなどを使用して抽出することを考慮に入れる必要があるため、メソッドは通常のエクストラクタよりも少し複雑です。
    while (isExtracting)
    {
        // 現在のノードとその子のクローンを作成して、コピーを取得します。
        Node cloneNode = currNode.Clone(true);
        isEndingNode = currNode.Equals(endNode);

        if ((isStartingNode || isEndingNode) && cloneNode.IsComposite)
        {
            // 各マーカーを個別に処理する必要があるため、代わりに個別のメソッドに渡します。
            if (isStartingNode)
            {
                ProcessMarker((CompositeNode)cloneNode, nodes, originalStartNode, isInclusive, isStartingNode, isEndingNode);
                isStartingNode = false;
            }

            // ブロックレベルの開始マーカーと終了マーカーは同じノードである可能性があるため、条件付きは分離する必要があります。
            if (isEndingNode)
            {
                ProcessMarker((CompositeNode)cloneNode, nodes, originalEndNode, isInclusive, isStartingNode, isEndingNode);
                isExtracting = false;
            }
        }
        else
            // ノードは開始マーカーでも終了マーカーでもありません。コピーをリストに追加するだけです。
            nodes.Add(cloneNode);

        // 次のノードに移動して抽出します。次のノードがnullの場合、残りのコンテンツが別のセクションにあることを意味します。
        if (currNode.NextSibling == null && isExtracting)
        {
            // 次のセクションに移動します。
            Section nextSection = (Section)currNode.GetAncestor(NodeType.Section).NextSibling;
            currNode = nextSection.Body.FirstChild;
        }
        else
        {
            // 本体の次のノードに移動します。
            currNode = currNode.NextSibling;
        }
    }

    // ノードマーカー間のノードを返します。
    return nodes;
}

以下に示すように、テキスト抽出操作を実行するために、ExtractContentメソッドにはいくつかのヘルパーメソッドも必要です。

public static List<Paragraph> ParagraphsByStyleName(Document doc, string styleName)
{
    // 指定されたスタイルの段落を収集するための配列を作成します。
    List<Paragraph> paragraphsWithStyle = new List<Paragraph>();

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

    // すべての段落を調べて、指定されたスタイルの段落を見つけます。
    foreach (Paragraph paragraph in paragraphs)
    {
        if (paragraph.ParagraphFormat.Style.Name == styleName)
            paragraphsWithStyle.Add(paragraph);
    }

    return paragraphsWithStyle;
}
private static void VerifyParameterNodes(Node startNode, Node endNode)
{
    // これらのチェックが行われる順序は重要です。
    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");

    // 終了ノードがDOMツリーの開始ノードの後にあることを確認してください
    // 最初にそれらが異なるセクションにあるかどうかを確認し、次にそれらが同じセクションの本体での位置を確認していない場合。
    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)
{
    // ノードが段落ノードまたはテーブルノードの子孫であり、段落またはテーブルでもないかどうかをテストします。段落またはテーブルではない場合、段落の子孫であるコメントクラス内の段落が可能です。
    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)
{
    // ブロックレベルのノードを扱っている場合は、それを含める必要があるかどうかを確認し、リストに追加してください。
    if (!IsInline(node))
    {
        // マーカーが同じノードである場合は、ノードを2回追加しないでください
        if (!(isStartMarker && isEndMarker))
        {
            if (isInclusive)
                nodes.Add(cloneNode);
        }
        return;
    }

    // マーカーがFieldStartノードである場合は、マーカーが含まれるかどうかを確認します。
    // 簡単にするために、FieldStartとFieldEndが同じ段落に表示されると仮定します。
    if (node.NodeType == NodeType.FieldStart)
    {
        // マーカーが開始ノードであり、含まれていない場合は、フィールドの最後にスキップします。
        // マーカーがエンドノードであり、それを含める場合は、フィールドが削除されないようにエンドフィールドに移動します。
        if ((isStartMarker && !isInclusive) || (!isStartMarker && isInclusive))
        {
            while (node.NextSibling != null && node.NodeType != NodeType.FieldEnd)
                node = node.NextSibling;

        }
    }

    // いずれかのマーカーがコメントの一部である場合、コメント自体を含めるには、ポインターをコメントに移動する必要があります
    // CommentRangeEndノードの後に見つかったノード。
    if (node.NodeType == NodeType.CommentRangeEnd)
    {
        while (node.NextSibling != null && node.NodeType != NodeType.Comment)
            node = node.NextSibling;

    }

    // 複製されたノードで対応するノードをインデックスで見つけて返します。
    // 開始ノードと終了ノードが同じである場合、一部の子ノードはすでに削除されている可能性があります。を引く
    // 適切なインデックスを取得するための違い。
    int indexDiff = node.ParentNode.ChildNodes.Count - cloneNode.ChildNodes.Count;

    // 子ノードの数は同じです。
    if (indexDiff == 0)
        node = cloneNode.ChildNodes[node.ParentNode.IndexOf(node)];
    else
        node = cloneNode.ChildNodes[node.ParentNode.IndexOf(node) - indexDiff];

    // マーカーまで/マーカーからノードを削除します。
    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();
    }

    // 処理後、複合ノードが空になる場合があります。含まれていない場合。
    if (!(isStartMarker && isEndMarker))
    {
        if (cloneNode.HasChildNodes)
            nodes.Add(cloneNode);
    }

}
public static Document GenerateDocument(Document srcDoc, ArrayList nodes)
{
    // 空白のドキュメントを作成します。
    Document dstDoc = new Document();
    // 空のドキュメントから最初の段落を削除します。
    dstDoc.FirstSection.Body.RemoveAllChildren();

    // リストから各ノードを新しいドキュメントにインポートします。ノードの元のフォーマットを保持します。
    NodeImporter importer = new NodeImporter(srcDoc, dstDoc, ImportFormatMode.KeepSourceFormatting);

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

    // 生成されたドキュメントを返します。
    return dstDoc;
}

これで、これらのメソッドを利用して、Word文書からテキストを抽出する準備が整いました。

Word文書の段落間のテキストを抽出する

WordDOCXドキュメントの2つの段落の間のコンテンツを抽出する方法を見てみましょう。以下は、C#でこの操作を実行するための手順です。

  • まず、Documentクラスを使用してWord文書をロードします。
  • Document.FirstSection.Body.GetChild(NodeType.PARAGRAPH, int, boolean)メソッドを使用して、開始段落と終了段落の参照を2つのオブジェクトに取得します。
  • ExtractContent(startPara, endPara, True)メソッドを呼び出して、ノードをオブジェクトに抽出します。
  • GenerateDocument(Document, extractedNodes)ヘルパーメソッドを呼び出して、抽出されたコンテンツで構成されるドキュメントを作成します。
  • 最後に、Document.Save(string)メソッドを使用して返されたドキュメントを保存します。

次のコードサンプルは、C#のWordドキュメントの7番目と11番目の段落の間のテキストを抽出する方法を示しています。

// Wordドキュメントを読み込む
Document doc = new Document("document.docx");

// ノードを収集します(GetChildメソッドは0ベースのインデックスを使用します)
Paragraph startPara = (Paragraph)doc.FirstSection.Body.GetChild(NodeType.Paragraph, 6, true);
Paragraph endPara = (Paragraph)doc.FirstSection.Body.GetChild(NodeType.Paragraph, 10, true);

// ドキュメント内のこれらのノード間のコンテンツを抽出します。これらのマーカーを抽出に含めます。
ArrayList extractedNodes = ExtractContent(startPara, endPara, true);

// コンテンツを新しいドキュメントに挿入し、ディスクに保存します。
Document dstDoc = GenerateDocument(doc, extractedNodes);
dstDoc.Save("output.docx");

Word文書内の異なるタイプのノード間でテキストを抽出する

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

  • Documentクラスを使用してWord文書をロードします。
  • **Document.FirstSection.Body.GetChild(NodeType, int, boolean)**メソッドを使用して、開始ノードと終了ノードの参照を2つのオブジェクトに取得します。
  • **ExtractContent(startPara, endPara, True)**メソッドを呼び出して、ノードをオブジェクトに抽出します。
  • **GenerateDocument(Document, extractedNodes)**ヘルパーメソッドを呼び出して、抽出されたコンテンツで構成されるドキュメントを作成します。
  • **Document.Save(string)**メソッドを使用して、返されたドキュメントを保存します。

次のコードサンプルは、C#で段落とテーブルの間のテキストを抽出する方法を示しています。

// 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);

// ドキュメント内のこれらのノード間のコンテンツを抽出します。これらのマーカーを抽出に含めます。
ArrayList extractedNodes = ExtractContent(startPara, endTable, true);

// コンテンツを新しいドキュメントに挿入し、ディスクに保存します。
Document dstDoc = GenerateDocument(doc, extractedNodes);
dstDoc.Save("output.docx");

スタイルに基づいて段落間のテキストを抽出する

次に、スタイルに基づいて段落間のコンテンツを抽出する方法を確認しましょう。デモンストレーションのために、Word文書の最初の「見出し1」と最初の「見出し3」の間のコンテンツを抽出します。次の手順は、C#でこれを実現する方法を示しています。

  • まず、Documentクラスを使用してWord文書をロードします。
  • 次に、**ParagraphsByStyleName(Document, “Heading 1”)**ヘルパーメソッドを使用して段落をオブジェクトに抽出します。
  • **ParagraphsByStyleName(Document, “Heading 3”)**ヘルパーメソッドを使用して、段落を別のオブジェクトに抽出します。
  • **ExtractContent(startPara, endPara, True)**メソッドを呼び出し、両方の段落配列の最初の要素を最初と2番目のパラメーターとして渡します。
  • **GenerateDocument(Document, extractedNodes)**ヘルパーメソッドを呼び出して、抽出されたコンテンツで構成されるドキュメントを作成します。
  • 最後に、**Document.Save(string)**メソッドを使用して返されたドキュメントを保存します。

次のコードサンプルは、スタイルに基づいて段落間のコンテンツを抽出する方法を示しています。

// Wordドキュメントを読み込む
Document doc = new Document("document.docx");

// それぞれの見出しスタイルを使用して、段落のリストを収集します。
List<Paragraph> parasStyleHeading1 = ParagraphsByStyleName(doc, "Heading 1");
List<Paragraph> parasStyleHeading3 = ParagraphsByStyleName(doc, "Heading 3");

// それらのスタイルの段落の最初のインスタンスを使用します。
Node startPara1 = (Node)parasStyleHeading1[0];
Node endPara1 = (Node)parasStyleHeading3[0];

// ドキュメント内のこれらのノード間のコンテンツを抽出します。これらのマーカーを抽出に含めないでください。
ArrayList extractedNodes = ExtractContent(startPara1, endPara1, false);

// コンテンツを新しいドキュメントに挿入し、ディスクに保存します。
Document dstDoc = GenerateDocument(doc, extractedNodes);
dstDoc.Save("output.docx");

続きを読む

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

無料のAPIライセンスを取得する

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

結論

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

関連項目

ヒント:Aspose PowerPoint to Word Converterは、一般的なプレゼンテーションからWord文書への変換プロセスを示しているため、確認することをお勧めします。