Wyodrębnij tekst z dokumentów MS Word w Javie

Wyodrębnianie tekstu z dokumentów programu Word jest często wykonywane w różnych scenariuszach. Na przykład, aby przeanalizować tekst, wyodrębnić określone sekcje dokumentu i połączyć je w jeden dokument i tak dalej. W tym artykule dowiesz się, jak programowo wyodrębnić tekst z dokumentów Worda w Javie. Ponadto omówimy, jak dynamicznie wyodrębniać zawartość między określonymi elementami, takimi jak akapity, tabele itp.

Biblioteka Java do wyodrębniania tekstu z dokumentów programu Word

Aspose.Words for Java to potężna biblioteka, która pozwala tworzyć od podstaw dokumenty MS Word. Ponadto pozwala manipulować istniejącymi dokumentami Word w celu szyfrowania, konwersji, ekstrakcji tekstu itp. Użyjemy tej biblioteki do wyodrębnienia tekstu z dokumentów Word DOCX lub DOC. Możesz pobrać JAR API lub zainstalować go przy użyciu następujących konfiguracji Mavena.

<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>

Ekstrakcja tekstu w Word DOC/DOCX w Javie

Dokument MS Word składa się z różnych elementów, takich jak akapity, tabele, obrazy itp. Dlatego wymagania dotyczące ekstrakcji tekstu mogą się różnić w zależności od scenariusza. Na przykład może być konieczne wyodrębnienie tekstu między akapitami, zakładkami, komentarzami itp.

Każdy typ elementu w Word DOC/DOCX jest reprezentowany jako węzeł. Dlatego, aby przetworzyć dokument, będziesz musiał bawić się węzłami. Zacznijmy więc i zobaczmy, jak wyodrębnić tekst z dokumentów programu Word w różnych scenariuszach.

Wyodrębnij tekst z Word DOC w Javie

W tej sekcji zamierzamy zaimplementować ekstraktor tekstu Java dla dokumentów programu Word, a przepływ pracy podczas wyodrębniania tekstu wyglądałby następująco:

  • Najpierw zdefiniujemy węzły, które chcemy uwzględnić w procesie ekstrakcji tekstu.
  • Następnie wyodrębnimy zawartość między określonymi węzłami (w tym lub z wyłączeniem węzłów początkowych i końcowych).
  • Na koniec wykorzystamy klon wyodrębnionych węzłów np. do stworzenia nowego dokumentu Worda składającego się z wyodrębnionej treści.

Napiszmy teraz metodę o nazwie extractContent, do której przekażemy węzły i kilka innych parametrów w celu przeprowadzenia ekstrakcji tekstu. Ta metoda przeanalizuje dokument i sklonuje węzły. Poniżej przedstawiono parametry, które przekażemy tej metodzie.

  1. startNode i endNode odpowiednio jako punkty początkowe i końcowe wyodrębniania zawartości. Mogą to być zarówno węzły na poziomie bloku (Akapit , Tabela), jak i na poziomie wiersza (np. Run, FieldStart, BookmarkStart itp.).
    1. Aby przekazać pole, należy przekazać odpowiedni obiekt FieldStart.
    2. Aby przekazać zakładki, należy przekazać węzły BookmarkStart i BookmarkEnd.
    3. W przypadku komentarzy należy używać węzłów CommentRangeStart i CommentRangeEnd.
  2. isInclusive określa, czy znaczniki są uwzględniane w ekstrakcji, czy nie. Jeśli ta opcja jest ustawiona na false i zostanie przekazany ten sam węzeł lub kolejne węzły, zwrócona zostanie pusta lista.

Poniżej przedstawiono kompletną implementację metody extractContent, która wyodrębnia zawartość między przekazywanymi węzłami.

// Pełne przykłady i pliki danych można znaleźć na stronie https://github.com/aspose-words/Aspose.Words-for-Java
public static ArrayList extractContent(Node startNode, Node endNode, boolean isInclusive) throws Exception {
    // Najpierw sprawdź, czy węzły przekazane do tej metody są prawidłowe do użycia.
    verifyParameterNodes(startNode, endNode);

    // Utwórz listę do przechowywania wyodrębnionych węzłów.
    ArrayList nodes = new ArrayList();

    // Zachowaj zapis oryginalnych węzłów przekazanych do tej metody, abyśmy mogli w razie potrzeby podzielić węzły znaczników.
    Node originalStartNode = startNode;
    Node originalEndNode = endNode;

    // Wyodrębnij zawartość na podstawie węzłów na poziomie bloku (akapity i tabele). Przechodź przez węzły nadrzędne, aby je znaleźć.
    // Podzielimy zawartość pierwszego i ostatniego węzła w zależności od tego, czy węzły znaczników są wbudowane
    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;
    // Bieżący węzeł, który wyodrębniamy z dokumentu.
    Node currNode = startNode;

    // Rozpocznij wyodrębnianie zawartości. Przetwarzaj wszystkie węzły na poziomie bloku iw razie potrzeby rozdzielaj pierwszy i ostatni węzły, aby zachować formatowanie akapitów.
    // Metoda jest nieco bardziej złożona niż zwykły ekstraktor, ponieważ musimy wziąć pod uwagę wyodrębnianie za pomocą wbudowanych węzłów, pól, zakładek itp., Aby była naprawdę użyteczna.
    while (isExtracting) {
        // Sklonuj bieżący węzeł i jego elementy podrzędne, aby uzyskać kopię.
        /*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) {
            // Musimy przetworzyć każdy znacznik osobno, więc zamiast tego przekaż go osobnej metodzie.
            if (isStartingNode) {
                processMarker(cloneNode, nodes, originalStartNode, isInclusive, isStartingNode, isEndingNode);
                isStartingNode = false;
            }

            // Warunek musi być oddzielny, ponieważ znaczniki początku i końca na poziomie bloku mogą być tym samym węzłem.
            if (isEndingNode) {
                processMarker(cloneNode, nodes, originalEndNode, isInclusive, isStartingNode, isEndingNode);
                isExtracting = false;
            }
        } else
            // Węzeł nie jest znacznikiem początkowym ani końcowym, po prostu dodaj kopię do listy.
            nodes.add(cloneNode);

        // Przejdź do następnego węzła i wyodrębnij go. Jeśli następny węzeł ma wartość null, oznacza to, że reszta treści znajduje się w innej sekcji.
        if (currNode.getNextSibling() == null && isExtracting) {
            // Przejdź do następnej sekcji.
            Section nextSection = (Section) currNode.getAncestor(NodeType.SECTION).getNextSibling();
            currNode = nextSection.getBody().getFirstChild();
        } else {
            // Przejdź do następnego węzła w ciele.
            currNode = currNode.getNextSibling();
        }
    }

    // Zwróć węzły między znacznikami węzłów.
    return nodes;
}

Niektóre metody pomocnicze są również wymagane przez metodę extractContent do wykonania operacji wyodrębniania tekstu, które podano poniżej.

/**
 * Sprawdza, czy parametry wejściowe są poprawne i czy można ich użyć. Zgłasza wyjątek
 * jeśli jest jakiś problem.
 */
private static void verifyParameterNodes(Node startNode, Node endNode) throws Exception {
	// Kolejność przeprowadzania tych kontroli jest ważna.
	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");

	// Sprawdź, czy węzeł końcowy znajduje się za węzłem początkowym w drzewie DOM
	// Najpierw sprawdź, czy są w różnych sekcjach, a jeśli nie, sprawdź
	// ich położenie w ciele tej samej sekcji, w której się znajdują.
	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");
}

/**
 * Sprawdza, czy przekazany węzeł jest węzłem wbudowanym.
 */
private static boolean isInline(Node node) throws Exception {
	// Sprawdź, czy węzeł jest potomkiem węzła akapitu lub tabeli, a także nie jest a
	// akapit lub tabela akapit wewnątrz klasy komentarza, który jest potomkiem
	// paragraf jest możliwy.
	return ((node.getAncestor(NodeType.PARAGRAPH) != null || node.getAncestor(NodeType.TABLE) != null)
			&& !(node.getNodeType() == NodeType.PARAGRAPH || node.getNodeType() == NodeType.TABLE));
}

/**
 * Usuwa zawartość przed lub po znaczniku w sklonowanym węźle, w zależności od tego
 * od rodzaju znacznika.
 */
private static void processMarker(CompositeNode cloneNode, ArrayList nodes, Node node, boolean isInclusive,
		boolean isStartMarker, boolean isEndMarker) throws Exception {
	// Jeśli mamy do czynienia z węzłem na poziomie bloku, po prostu sprawdź, czy powinien zostać uwzględniony
	// i dodaj go do listy.
	if (!isInline(node)) {
		// Nie dodawaj węzła dwa razy, jeśli znaczniki są tym samym węzłem
		if (!(isStartMarker && isEndMarker)) {
			if (isInclusive)
				nodes.add(cloneNode);
		}
		return;
	}

	// Jeśli znacznik jest węzłem FieldStart, sprawdź, czy ma być uwzględniony, czy nie.
	// Dla uproszczenia zakładamy, że FieldStart i FieldEnd pojawiają się w tym samym
	// ustęp.
	if (node.getNodeType() == NodeType.FIELD_START) {
		// Jeśli znacznik jest węzłem początkowym i nie jest uwzględniony, przejdź do końca
		// pole.
		// Jeśli znacznik jest węzłem końcowym i ma być uwzględniony, przejdź do końca
		// pole, aby pole nie zostało usunięte.
		if ((isStartMarker && !isInclusive) || (!isStartMarker && isInclusive)) {
			while (node.getNextSibling() != null && node.getNodeType() != NodeType.FIELD_END)
				node = node.getNextSibling();

		}
	}

	// Jeśli któryś ze znaczników jest częścią komentarza, wówczas dołączymy sam komentarz
	// musisz przesunąć wskaźnik do przodu do Komentarza
	// węzeł znaleziony za węzłem CommentRangeEnd.
	if (node.getNodeType() == NodeType.COMMENT_RANGE_END) {
		while (node.getNextSibling() != null && node.getNodeType() != NodeType.COMMENT)
			node = node.getNextSibling();

	}

	// Znajdź odpowiedni węzeł w naszym sklonowanym węźle według indeksu i zwróć go.
	// Jeśli węzeł początkowy i końcowy są takie same, niektóre węzły podrzędne mogą już mieć
	// został usunięty. Odejmij
	// różnicę, aby uzyskać właściwy indeks.
	int indexDiff = node.getParentNode().getChildNodes().getCount() - cloneNode.getChildNodes().getCount();

	// Liczba węzłów podrzędnych identyczna.
	if (indexDiff == 0)
		node = cloneNode.getChildNodes().get(node.getParentNode().indexOf(node));
	else
		node = cloneNode.getChildNodes().get(node.getParentNode().indexOf(node) - indexDiff);

	// Usuń węzły do/od znacznika.
	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();
	}

	// Po przetworzeniu węzeł złożony może stać się pusty. Jeśli tak, nie dołączaj
	// to.
	if (!(isStartMarker && isEndMarker)) {
		if (cloneNode.hasChildNodes())
			nodes.add(cloneNode);
	}
}

public static Document generateDocument(Document srcDoc, ArrayList nodes) throws Exception {

	// Utwórz pusty dokument.
	Document dstDoc = new Document();
	// Usuń pierwszy akapit z pustego dokumentu.
	dstDoc.getFirstSection().getBody().removeAllChildren();

	// Zaimportuj każdy węzeł z listy do nowego dokumentu. Zachowaj oryginał
	// formatowanie węzła.
	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);
	}

	// Zwróć wygenerowany dokument.
	return dstDoc;
}

Teraz jesteśmy gotowi do wykorzystania tych metod i wyodrębnienia tekstu z dokumentu Word.

Java Wyodrębnij tekst między akapitami w dokumencie Word DOC

Zobaczmy, jak wyodrębnić zawartość między dwoma akapitami w dokumencie Word DOCX. Poniżej przedstawiono kroki, aby wykonać tę operację w Javie.

  • Najpierw załaduj dokument programu Word przy użyciu klasy Document.
  • Uzyskaj odwołanie do akapitu początkowego i końcowego do dwóch obiektów za pomocą metody Document.getFirstSection().getChild(NodeType.PARAGRAPH, int, bool).
  • Wywołaj metodę extractContent(startPara, endPara, true), aby wyodrębnić węzły do obiektu.
  • Wywołaj metodę pomocnika generateDocument(Document, extractedNodes), aby utworzyć dokument składający się z wyodrębnionej treści.
  • Na koniec zapisz zwrócony dokument przy użyciu metody Document.save(String).

Poniższy przykładowy kod pokazuje, jak wyodrębnić tekst między 7. a 11. akapitem w Word DOCX w Javie.

// Załaduj dokument
Document doc = new Document("TestFile.doc");

// Zbierz węzły. Metoda GetChild używa indeksu opartego na 0
Paragraph startPara = (Paragraph) doc.getFirstSection().getChild(NodeType.PARAGRAPH, 6, true);
Paragraph endPara = (Paragraph) doc.getFirstSection().getChild(NodeType.PARAGRAPH, 10, true);
// Wyodrębnij zawartość między tymi węzłami w dokumencie. Uwzględnij te
// znaczniki w ekstrakcji.
ArrayList extractedNodes = extractContent(startPara, endPara, true);

// Wstaw zawartość do nowego oddzielnego dokumentu i zapisz ją na dysku.
Document dstDoc = generateDocument(doc, extractedNodes);
dstDoc.save("output.doc");

Java Wyodrębnij tekst z DOC - między różnymi typami węzłów

Możesz także wyodrębniać zawartość między różnymi typami węzłów. Dla demonstracji wyodrębnijmy zawartość między akapitem a tabelą i zapiszmy ją w nowym dokumencie programu Word. Poniżej przedstawiono kroki, aby wyodrębnić tekst między różnymi węzłami w dokumencie Word w Javie.

  • Załaduj dokument programu Word przy użyciu klasy Document.
  • Pobierz odwołanie do węzła początkowego i końcowego do dwóch obiektów za pomocą metody Document.getFirstSection().getChild(NodeType, int, bool).
  • Wywołaj metodę extractContent(startPara, endPara, true), aby wyodrębnić węzły do obiektu.
  • Wywołaj metodę pomocnika generateDocument(Document, extractedNodes), aby utworzyć dokument składający się z wyodrębnionej treści.
  • Zapisz zwrócony dokument przy użyciu metody Document.save(String).

Poniższy przykładowy kod pokazuje, jak wyodrębnić tekst między akapitem a tabelą w DOCX przy użyciu języka Java.

// Załaduj dokumenty
Document doc = new Document("TestFile.doc");

// Uzyskaj odniesienie do akapitu początkowego
Paragraph startPara = (Paragraph) doc.getLastSection().getChild(NodeType.PARAGRAPH, 2, true);
Table endTable = (Table) doc.getLastSection().getChild(NodeType.TABLE, 0, true);

// Wyodrębnij zawartość między tymi węzłami w dokumencie. Uwzględnij te znaczniki w ekstrakcji.
ArrayList extractedNodes = extractContent(startPara, endTable, true);

// Odwróćmy tablicę, aby ułatwić wstawianie zawartości z powrotem do dokumentu.
Collections.reverse(extractedNodes);

while (extractedNodes.size() > 0) {
    // Wstaw ostatni węzeł z odwróconej listy
    endTable.getParentNode().insertAfter((Node) extractedNodes.get(0), endTable);
    // Usuń ten węzeł z listy po wstawieniu.
    extractedNodes.remove(0);
}

// Zapisz wygenerowany dokument na dysku.
doc.save("output.doc");

Java Wyodrębnianie tekstu z DOCX - Między akapitami na podstawie stylów

Sprawdźmy teraz, jak wyodrębnić treść między akapitami na podstawie stylów. W celu demonstracji wyodrębnimy zawartość między pierwszym „Nagłówkiem 1” a pierwszym „Nagłówkiem 3” w dokumencie programu Word. Poniższe kroki pokazują, jak to osiągnąć w Javie.

  • Najpierw załaduj dokument programu Word przy użyciu klasy Document.
  • Następnie wyodrębnij akapity do obiektu za pomocą metody pomocniczej ParagrafyByStyleName(Document, “Heading 1”).
  • Wyodrębnij akapity do innego obiektu za pomocą metody pomocniczej ParagrafyByStyleName(Document, “Heading 3”).
  • Wywołaj metodę extractContent(startPara, endPara, true) i przekaż pierwsze elementy w obu tablicach akapitów jako pierwszy i drugi parametr.
  • Wywołaj metodę pomocnika generateDocument(Document, extractedNodes), aby utworzyć dokument składający się z wyodrębnionej treści.
  • Na koniec zapisz zwrócony dokument przy użyciu metody Document.save(String).

Poniższy przykład kodu pokazuje, jak wyodrębnić zawartość między akapitami na podstawie stylów.

// Załaduj dokument
Document doc = new Document(dataDir + "TestFile.doc");

// Zbierz listę akapitów, używając odpowiednich stylów nagłówków.
ArrayList parasStyleHeading1 = paragraphsByStyleName(doc, "Heading 1");
ArrayList parasStyleHeading3 = paragraphsByStyleName(doc, "Heading 3");

// Użyj pierwszego wystąpienia akapitów z tymi stylami.
Node startPara1 = (Node) parasStyleHeading1.get(0);
Node endPara1 = (Node) parasStyleHeading3.get(0);

// Wyodrębnij zawartość między tymi węzłami w dokumencie. Nie uwzględniaj tych znaczników w ekstrakcji.
ArrayList extractedNodes = extractContent(startPara1, endPara1, false);

// Wstaw zawartość do nowego oddzielnego dokumentu i zapisz ją na dysku.
Document dstDoc = generateDocument(doc, extractedNodes);
dstDoc.save("output.doc");

Ekstraktor tekstu Java Word — Czytaj więcej

Możesz zapoznać się z innymi scenariuszami wyodrębniania tekstu z dokumentów programu Word, korzystając z tego artykułu w dokumentacji.

Java API do wyodrębniania tekstu z DOC/DOCX — Uzyskaj bezpłatną licencję

Możesz uzyskać tymczasową licencję na używanie Aspose.Words for Java bez ograniczeń ewaluacyjnych.

Wniosek

W tym artykule nauczyłeś się, jak wyodrębnić tekst z MS Word DOC DOCX w Javie. Ponadto widziałeś, jak programowo wyodrębniać zawartość między podobnymi lub różnymi typami węzłów w dokumencie programu Word. W ten sposób możesz zbudować własny ekstraktor tekstu MS Word w Javie. Poza tym możesz poznać inne funkcje Aspose.Words for Java, korzystając z dokumentacji. Jeśli masz jakieś pytania, daj nam znać za pośrednictwem naszego forum.

Zobacz też