C#의 MS Word 문서에서 텍스트 추출

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

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

Aspose.Words for .NET는 처음부터 MS Word 문서를 만들 수 있는 강력한 라이브러리입니다. 또한 암호화, 변환, 텍스트 추출 등을 위해 기존 Word 문서를 조작할 수 있습니다. 이 라이브러리를 사용하여 Word DOCX 또는 DOC 문서에서 텍스트를 추출합니다. API의 DLL을 다운로드하거나 패키지 관리자 콘솔을 사용하여 NuGet에서 직접 설치할 수 있습니다.

PM> Install-Package Aspose.Words

C#을 사용하여 Word 문서에서 텍스트 추출

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

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

C#의 Word 문서에서 텍스트 추출

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

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

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

  1. StartNode 및 EndNode는 각각 콘텐츠 추출을 위한 시작 및 종료 지점입니다. 이들은 블록 수준(Paragraph, Table) 또는 인라인 수준(예: Run, FieldStart, BookmarkStart 등) 노드일 수 있습니다.
    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))
    {
        // 마커가 동일한 노드인 경우 노드를 두 번 추가하지 마십시오.
        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 문서에서 단락 사이의 텍스트 추출

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

  • 먼저 Document 클래스를 사용하여 Word 문서를 로드합니다.
  • Document.FirstSection.Body.GetChild(NodeType.PARAGRAPH, int, boolean) 메서드를 사용하여 시작 및 끝 단락의 참조를 두 개체로 가져옵니다.
  • ExtractContent(startPara, endPara, True) 메서드를 호출하여 노드를 객체로 추출합니다.
  • GenerateDocument(Document, extractNodes) 도우미 메서드를 호출하여 추출된 내용으로 구성된 문서를 만듭니다.
  • 마지막으로 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) 메서드를 사용하여 시작 및 끝 노드의 참조를 두 개체로 가져옵니다.
  • ExtractContent(startPara, endPara, True) 메서드를 호출하여 노드를 객체로 추출합니다.
  • GenerateDocument(Document, extractNodes) 도우미 메서드를 호출하여 추출된 내용으로 구성된 문서를 만듭니다.
  • 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) 메서드를 호출하고 두 단락 배열의 첫 번째 요소를 첫 번째 및 두 번째 매개변수로 전달합니다.
  • GenerateDocument(Document, extractNodes) 도우미 메서드를 호출하여 추출된 내용으로 구성된 문서를 만듭니다.
  • 마지막으로 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");

더 읽기

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

무료 API 라이선스 받기

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

결론

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

또한보십시오

팁: Aspose PowerPoint에서 Word로 변환기는 인기 있는 프레젠테이션을 Word 문서로 변환하는 과정을 보여주기 때문에 확인하는 것이 좋습니다.