Вилучення тексту з документів MS Word на C#

Вилучення тексту з документів Word часто виконується в різних сценаріях. Наприклад, щоб проаналізувати текст, виділити окремі розділи документа та об’єднати їх в один документ тощо. У цій статті ви дізнаєтесь, як видобувати текст із документів Word на C#. Крім того, ми розглянемо, як видобувати вміст між певними елементами документа Word, такими як абзаци, таблиці тощо.

Як витягти текст із Word на C#

Для отримання тексту з документів Word ми будемо використовувати Aspose.Words for .NET. Це потужна бібліотека, яка дозволяє створювати та обробляти документи MS Word. Ви можете легко отримати його безкоштовну ліцензію і видобувати текст із документів Word без будь-яких обмежень.

Завантажте DLL або встановіть бібліотеку безпосередньо з NuGet за допомогою консолі менеджера пакетів.

PM> Install-Package Aspose.Words

Вилучення тексту з документів Word

Документ MS Word складається з різних елементів, які включають абзаци, таблиці, зображення тощо. Тому вимоги до вилучення тексту можуть відрізнятися від одного сценарію до іншого. Наприклад, вам може знадобитися витягти текст між абзацами, закладки, коментарі тощо.

Кожен тип елемента в документі Word представлено у вигляді вузла. Тому для обробки документа доведеться погратися з вузлами. Тож давайте почнемо й подивимось, як витягувати текст із документів Word у різних сценаріях.

Отримати текст із Word DOCX у C#

У цьому розділі ми збираємося запровадити екстрактор тексту C# для документів Word, і робочий процес вилучення тексту буде таким:

  • Спочатку ми визначимо вузли, які ми хочемо включити в процес вилучення тексту.
  • Потім ми витягнемо вміст між вказаними вузлами (включаючи або виключаючи початковий і кінцевий вузли).
  • Нарешті, ми використаємо клон витягнутих вузлів, наприклад, щоб створити новий документ Word, що складається з вилученого вмісту.

Давайте тепер напишемо метод під назвою ExtractContent, якому ми передамо вузли та деякі інші параметри для виконання вилучення тексту. Цей метод розбере документ і клонує вузли. Нижче наведено параметри, які ми передамо цьому методу.

  1. StartNode і EndNode як початкова і кінцева точки для вилучення вмісту відповідно. Це можуть бути вузли рівня блоку (Абзац, Таблиця) або вбудованого рівня (наприклад, Виконати, 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#.

  • Спочатку завантажте документ Word за допомогою класу Document.
  • Отримати посилання на початковий і кінцевий абзаци в два об’єкти за допомогою методу Document.FirstSection.Body.GetChild(NodeType.PARAGRAPH, int, boolean).
  • Викличте метод ExtractContent(startPara, endPara, True), щоб витягнути вузли в об’єкт.
  • Викличте допоміжний метод GenerateDocument(Document, extractedNodes), щоб створити документ із витягнутим вмістом.
  • Нарешті, збережіть повернутий документ за допомогою методу Document.Save(string).

У наведеному нижче прикладі коду показано, як витягнути текст між 7-м і 11-м абзацами в документі Word у C#.

// Завантажити документ 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. Нижче наведено кроки для виконання цієї операції.

  • Завантажте документ Word за допомогою класу Document.
  • Отримайте посилання на початковий і кінцевий вузли в два об’єкти за допомогою методу Document.FirstSection.Body.GetChild(NodeType, int, boolean).
  • Викличте метод 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");

Отримайте текст між абзацами на основі стилів

Давайте тепер перевіримо, як витягувати вміст між абзацами на основі стилів. Для демонстрації ми витягнемо вміст між першим «Заголовком 1» і першим «Заголовком 3» у документі Word. Наступні кроки демонструють, як досягти цього в C#.

  • Спочатку завантажте документ Word за допомогою класу Document.
  • Потім витягніть абзаци в об’єкт за допомогою допоміжного методу ParagraphsByStyleName(Document, “Heading 1”).
  • Витягніть абзаци в інший об’єкт за допомогою допоміжного методу ParagraphsByStyleName(Document, “Heading 3”).
  • Викличте метод ExtractContent(startPara, endPara, True) і передайте перші елементи в обох масивах абзаців як перший і другий параметри.
  • Викличте допоміжний метод 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");

Докладніше про вилучення тексту

Ви можете вивчити інші сценарії вилучення тексту з документів Word, використовуючи цю статтю документації.

Безкоштовний екстрактор тексту MS Word

Ви можете отримати безкоштовну тимчасову ліцензію, щоб видобувати текст без обмежень оцінки.

Висновок

У цій статті ви дізналися, як видобувати текст із документів MS Word за допомогою C#. Крім того, ви бачили, як програмно видобувати вміст між подібними або різними типами вузлів у документі Word. Таким чином, ви можете створити свій власний екстрактор тексту MS Word на C#. Крім того, ви можете вивчити інші функції Aspose.Words for .NET за допомогою документації. Якщо у вас виникнуть запитання, не соромтеся повідомити нас через наш форум.

Дивись також

Порада. Ви можете перевірити Aspose PowerPoint to Word Converter, оскільки він демонструє популярний процес перетворення презентацій у документи Word.