Извлечение текста из документов Word часто выполняется по разным сценариям. Например, для анализа текста, выделения отдельных разделов документа и объединения их в один документ и т.д. В этой статье вы узнаете, как программно извлекать текст из документов Word на Java. Кроме того, мы рассмотрим, как динамически извлекать содержимое между определенными элементами, такими как абзацы, таблицы и т. д.
- Библиотека Java для извлечения текста из Word DOC DOCX
- Извлечение текста Java в документах Word
- Извлечь текст из документа Word в Java
Библиотека Java для извлечения текста из документов Word
Aspose.Words for Java — мощная библиотека, позволяющая создавать документы MS Word с нуля. Кроме того, она позволяет вам манипулировать существующими документами Word для шифрования, преобразования, извлечения текста и т. д. Мы будем использовать эту библиотеку для извлечения текста из документов Word DOCX или DOC. Вы можете скачать JAR-файл API или установить его, используя следующие конфигурации 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>
Извлечение текста в Word DOC/DOCX на Java
Документ MS Word состоит из различных элементов, включая абзацы, таблицы, изображения и т. д. Поэтому требования к извлечению текста могут различаться в зависимости от сценария. Например, вам может понадобиться извлечь текст между абзацами, закладками, комментариями и т. д.
Каждый тип элемента в Word DOC/DOCX представлен как узел. Поэтому для обработки документа придется поиграться с узлами. Итак, давайте начнем и посмотрим, как извлечь текст из документов Word в разных сценариях.
Извлечь текст из Word DOC в Java
В этом разделе мы собираемся реализовать экстрактор текста Java для документов Word, и рабочий процесс извлечения текста будет следующим:
- Во-первых, мы определим узлы, которые мы хотим включить в процесс извлечения текста.
- Затем мы извлечем содержимое между указанными узлами (включая или исключая начальный и конечный узлы).
- Наконец, мы будем использовать клон извлеченных узлов, например, для создания нового документа Word, состоящего из извлеченного содержимого.
Давайте теперь напишем метод с именем extractContent, которому мы будем передавать узлы и некоторые другие параметры для выполнения извлечения текста. Этот метод будет анализировать документ и клонировать узлы. Ниже приведены параметры, которые мы передадим этому методу.
- startNode и endNode в качестве начальной и конечной точек для извлечения содержимого соответственно. Это могут быть узлы как уровня блока (абзац, таблица), так и встроенного уровня (например, Run, FieldStart, BookmarkStart и т. д.).
- Чтобы передать поле, вы должны передать соответствующий объект FieldStart.
- Для передачи закладок необходимо передать узлы BookmarkStart и BookmarkEnd.
- Для комментариев следует использовать узлы CommentRangeStart и CommentRangeEnd.
- 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.
Java Извлечение текста между абзацами в Word DOC
Давайте посмотрим, как извлечь содержимое между двумя абзацами в документе Word DOCX. Ниже приведены шаги для выполнения этой операции в Java.
- Сначала загрузите документ Word, используя класс Document.
- Получите ссылку на начальный и конечный абзацы на два объекта, используя метод Document.getFirstSection().getChild(NodeType.PARAGRAPH, int, bool).
- Вызовите метод extractContent(startPara, endPara, true) для извлечения узлов в объект.
- Вызовите вспомогательный метод generateDocument(Document, ExtractedNodes) для создания документа, состоящего из извлеченного содержимого.
- Наконец, сохраните возвращенный документ с помощью метода Document.save(String).
В следующем примере кода показано, как извлечь текст между 7-м и 11-м абзацами в Word DOCX на Java.
// Загрузить документ
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. Ниже приведены шаги для извлечения текста между разными узлами в документе Word на Java.
- Загрузите документ Word, используя класс Document.
- Получите ссылку на начальный и конечный узлы в два объекта, используя метод Document.getFirstSection().getChild(NodeType, int, bool).
- Вызовите метод extractContent(startPara, endPara, true) для извлечения узлов в объект.
- Вызовите вспомогательный метод generateDocument(Document, ExtractedNodes) для создания документа, состоящего из извлеченного содержимого.
- Сохраните возвращенный документ с помощью метода Document.save(String).
В следующем примере кода показано, как извлечь текст между абзацем и таблицей в DOCX с помощью Java.
// Загрузить документы
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 — между абзацами на основе стилей
Давайте теперь посмотрим, как извлечь содержимое между абзацами на основе стилей. Для демонстрации мы собираемся извлечь содержимое между первым «Заголовком 1» и первым «Заголовком 3» в документе Word. Следующие шаги демонстрируют, как добиться этого в Java.
- Сначала загрузите документ Word, используя класс Document.
- Затем извлеките абзацы в объект с помощью вспомогательного метода parapsByStyleName(Document, «Заголовок 1»).
- Извлеките абзацы в другой объект с помощью вспомогательного метода parapsByStyleName(Document, «Заголовок 3»).
- Вызовите метод extractContent(startPara, endPara, true) и передайте первые элементы в обоих массивах абзацев в качестве первого и второго параметров.
- Вызовите вспомогательный метод 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 — Подробнее
Вы можете изучить другие сценарии извлечения текста из документов Word, используя эту статью документации.
Java API для извлечения текста из DOC/DOCX — получите бесплатную лицензию
Вы можете получить временную лицензию на использование Aspose.Words for Java без ограничений на пробную версию.
Вывод
В этой статье вы узнали, как извлечь текст из MS Word DOC DOCX в Java. Кроме того, вы видели, как программно извлекать содержимое между похожими или разными типами узлов в документе Word. Таким образом, вы можете создать свой собственный экстрактор текста MS Word на Java. Кроме того, вы можете изучить другие возможности Aspose.Words for Java, используя документацию. Если у вас возникнут какие-либо вопросы, сообщите нам об этом через наш форум.