Extrahera text från MS Word-dokument i Java

I olika fall kan du behöva extrahera text från Word-dokument. Till exempel för textanalys, extrahering av särskilda avsnitt, etc. För sådana fall ger den här artikeln en snabb och lättimplementerad metod för att extrahera text från Word-dokument i Java. Det kommer också att göra det möjligt för dig att extrahera text mellan olika avsnitt/element i ett Word-dokument.

Java-bibliotek för att extrahera text från Word-dokument

Aspose.Words for Java är ett kraftfullt bibliotek som låter dig skapa MS Word-dokument från grunden. Dessutom låter den dig manipulera befintliga Word-dokument för kryptering, konvertering, textextraktion, etc. Vi kommer att använda detta bibliotek för att extrahera text från Word DOCX- eller DOC-dokument. Du kan ladda ner API:s JAR eller installera den med hjälp av följande Maven-konfigurationer.

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

Förstå ett Word-dokuments struktur

Ett MS Word-dokument består av olika element, som inkluderar stycken, tabeller, bilder etc. Därför kan kraven på textextraktion variera från ett scenario till ett annat. Du kan till exempel behöva extrahera text mellan stycken, bokmärken, kommentarer osv.

Varje typ av element i ett Word DOC/DOCX representeras som en nod. Därför, för att bearbeta ett dokument, måste du leka med noderna. Så låt oss börja och se hur man extraherar text från Word-dokument i olika scenarier.

Extrahera text från en Word-doc i Java

I det här avsnittet kommer vi att implementera en Java-textextraktor för Word-dokument och arbetsflödet för textextraktion skulle vara som följer:

  • Först kommer vi att definiera de noder som vi vill inkludera i textextraktionsprocessen.
  • Sedan extraherar vi innehållet mellan de angivna noderna (inklusive eller exkluderar start- och slutnoderna).
  • Slutligen kommer vi att använda klonen av extraherade noder, till exempel för att skapa ett nytt Word-dokument som består av extraherat innehåll.

Låt oss nu skriva en metod som heter extractContent som vi skickar noderna och några andra parametrar till för att utföra textextraktionen. Denna metod kommer att analysera dokumentet och klona noderna. Följande är parametrarna som vi kommer att skicka till denna metod.

  1. startNode och endNode som start- och slutpunkter för utvinningen av innehållet. Dessa kan vara noder på både blocknivå (Paragraph , Table) eller inlinenivå (t.ex. Run, FieldStart, BookmarkStart etc.).
    1. För att skicka ett fält måste du skicka det motsvarande FieldStart-objektet.
    2. För att skicka bokmärken ska noderna BookmarkStart och BookmarkEnd passeras.
    3. För kommentarer ska noderna CommentRangeStart och CommentRangeEnd användas.
  2. isInclusive definierar om markörerna ingår i extraktionen eller inte. Om det här alternativet är inställt på false och samma nod eller på varandra följande noder skickas, kommer en tom lista att returneras.

Följande är den fullständiga implementeringen av metoden extractContent som extraherar innehållet mellan noderna som skickas.

// För fullständiga exempel och datafiler, gå till https://github.com/aspose-words/Aspose.Words-for-Java
public static ArrayList extractContent(Node startNode, Node endNode, boolean isInclusive) throws Exception {
    // Kontrollera först att noderna som skickas till denna metod är giltiga för användning.
    verifyParameterNodes(startNode, endNode);

    // Skapa en lista för att lagra de extraherade noderna.
    ArrayList nodes = new ArrayList();

    // Håll ett register över de ursprungliga noderna som skickats till denna metod så att vi kan dela upp markörnoder om det behövs.
    Node originalStartNode = startNode;
    Node originalEndNode = endNode;

    // Extrahera innehåll baserat på noder på blocknivå (stycken och tabeller). Gå igenom föräldranoder för att hitta dem.
    // Vi kommer att dela upp innehållet i första och sista noder beroende på om markörnoderna är inline
    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;
    // Den aktuella noden vi extraherar från dokumentet.
    Node currNode = startNode;

    // Börja extrahera innehåll. Bearbeta alla noder på blocknivå och dela specifikt de första och sista noderna när det behövs så att styckeformateringen behålls.
    // Metoden är lite mer komplex än en vanlig extraherare eftersom vi måste ta hänsyn till att extrahera med hjälp av inline-noder, fält, bokmärken etc för att göra den riktigt användbar.
    while (isExtracting) {
        // Klona den aktuella noden och dess underordnade för att få en kopia.
        /*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) {
            // Vi måste bearbeta varje markör separat så skicka den till en separat metod istället.
            if (isStartingNode) {
                processMarker(cloneNode, nodes, originalStartNode, isInclusive, isStartingNode, isEndingNode);
                isStartingNode = false;
            }

            // Villkor måste vara separata eftersom blocknivåns start- och slutmarkörer kanske är samma nod.
            if (isEndingNode) {
                processMarker(cloneNode, nodes, originalEndNode, isInclusive, isStartingNode, isEndingNode);
                isExtracting = false;
            }
        } else
            // Noden är inte en start- eller slutmarkör, lägg bara till kopian i listan.
            nodes.add(cloneNode);

        // Flytta till nästa nod och extrahera den. Om nästa nod är null betyder det att resten av innehållet finns i ett annat avsnitt.
        if (currNode.getNextSibling() == null && isExtracting) {
            // Flytta till nästa avsnitt.
            Section nextSection = (Section) currNode.getAncestor(NodeType.SECTION).getNextSibling();
            currNode = nextSection.getBody().getFirstChild();
        } else {
            // Flytta till nästa nod i kroppen.
            currNode = currNode.getNextSibling();
        }
    }

    // Returnera noderna mellan nodmarkörerna.
    return nodes;
}

Vissa hjälpmetoder krävs också av metoden extractContent för att utföra textextraktionsoperationen, som ges nedan.

/**
 * Kontrollerar att ingångsparametrarna är korrekta och kan användas. Kastar ett undantag
 * om det är något problem.
 */
private static void verifyParameterNodes(Node startNode, Node endNode) throws Exception {
	// Ordningen i vilken dessa kontroller görs är viktig.
	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");

	// Kontrollera att slutnoden ligger efter startnoden i DOM-trädet
	// Kontrollera först om de finns i olika sektioner, sedan om de inte är kontrollerade
	// deras position i kroppen av samma sektion de befinner sig i.
	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");
}

/**
 * Kontrollerar om en nod som passerats är en inline-nod.
 */
private static boolean isInline(Node node) throws Exception {
	// Testa om noden är avstammande från en paragraf- eller tabellnod och inte heller är en
	// stycke eller en tabell ett stycke inuti en kommentarsklass som är härrörande från
	// en pararaph är möjlig.
	return ((node.getAncestor(NodeType.PARAGRAPH) != null || node.getAncestor(NodeType.TABLE) != null)
			&& !(node.getNodeType() == NodeType.PARAGRAPH || node.getNodeType() == NodeType.TABLE));
}

/**
 * Tar bort innehållet före eller efter markören i den klonade noden beroende på
 * på typen av markör.
 */
private static void processMarker(CompositeNode cloneNode, ArrayList nodes, Node node, boolean isInclusive,
		boolean isStartMarker, boolean isEndMarker) throws Exception {
	// Om vi har att göra med en nod på blocknivå, se bara om den ska inkluderas
	// och lägg till den i listan.
	if (!isInline(node)) {
		// Lägg inte till noden två gånger om markörerna är samma nod
		if (!(isStartMarker && isEndMarker)) {
			if (isInclusive)
				nodes.add(cloneNode);
		}
		return;
	}

	// Om en markör är en FieldStart-nod kontrollera om den ska inkluderas eller inte.
	// Vi antar för enkelhetens skull att FieldStart och FieldEnd visas i samma
	// paragraf.
	if (node.getNodeType() == NodeType.FIELD_START) {
		// Om markören är en startnod och inte ingår, hoppa till slutet av
		// fältet.
		// Om markören är en slutnod och den ska inkluderas, flytta till slutet
		// fältet så att fältet inte tas bort.
		if ((isStartMarker && !isInclusive) || (!isStartMarker && isInclusive)) {
			while (node.getNextSibling() != null && node.getNodeType() != NodeType.FIELD_END)
				node = node.getNextSibling();

		}
	}

	// Om någon av markörerna är en del av en kommentar så för att inkludera själva kommentaren vi
	// måste flytta pekaren framåt till kommentaren
	// nod hittas efter noden CommentRangeEnd.
	if (node.getNodeType() == NodeType.COMMENT_RANGE_END) {
		while (node.getNextSibling() != null && node.getNodeType() != NodeType.COMMENT)
			node = node.getNextSibling();

	}

	// Hitta motsvarande nod i vår klonade nod efter index och returnera den.
	// Om start- och slutnoden är desamma kan vissa underordnade noder redan ha
	// tagits bort. Subtrahera
	// skillnad för att få rätt index.
	int indexDiff = node.getParentNode().getChildNodes().getCount() - cloneNode.getChildNodes().getCount();

	// Antalet barnnoder är identiskt.
	if (indexDiff == 0)
		node = cloneNode.getChildNodes().get(node.getParentNode().indexOf(node));
	else
		node = cloneNode.getChildNodes().get(node.getParentNode().indexOf(node) - indexDiff);

	// Ta bort noderna upp till/från markören.
	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();
	}

	// Efter bearbetning kan den sammansatta noden bli tom. Om det har inte inkludera
	// Det.
	if (!(isStartMarker && isEndMarker)) {
		if (cloneNode.hasChildNodes())
			nodes.add(cloneNode);
	}
}

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

	// Skapa ett tomt dokument.
	Document dstDoc = new Document();
	// Ta bort första stycket från det tomma dokumentet.
	dstDoc.getFirstSection().getBody().removeAllChildren();

	// Importera varje nod från listan till det nya dokumentet. Behåll originalet
	// formatering av noden.
	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);
	}

	// Returnera det genererade dokumentet.
	return dstDoc;
}

Nu är vi redo att använda dessa metoder och extrahera text från ett Word-dokument.

Extrahera text mellan stycken i ett Word-dokument

Låt oss se hur man extraherar innehåll mellan två stycken i ett Word DOCX-dokument. Följande är stegen för att utföra denna operation i Java.

  • Ladda först Word-dokumentet med Document class.
  • Hämta referens till start- och slutstyckena till två objekt med metoden Document.getFirstSection().getChild(NodeType.PARAGRAPH, int, bool).
  • Anropa metoden extractContent(startPara, endPara, true) för att extrahera noderna till ett objekt.
  • Anrop genereraDocument(Document, extractedNodes) hjälparmetod för att skapa dokument som består av det extraherade innehållet.
  • Slutligen sparar du det returnerade dokumentet med metoden Document.save(String).

Följande kodexempel visar hur man extraherar text mellan 7:e och 11:e styckena i en Word DOCX i Java.

// Ladda dokument
Document doc = new Document("TestFile.doc");

// Samla noderna. GetChild-metoden använder 0-baserat index
Paragraph startPara = (Paragraph) doc.getFirstSection().getChild(NodeType.PARAGRAPH, 6, true);
Paragraph endPara = (Paragraph) doc.getFirstSection().getChild(NodeType.PARAGRAPH, 10, true);
// Extrahera innehållet mellan dessa noder i dokumentet. Inkludera dessa
// markörer i extraktionen.
ArrayList extractedNodes = extractContent(startPara, endPara, true);

// Infoga innehållet i ett nytt separat dokument och spara det på disk.
Document dstDoc = generateDocument(doc, extractedNodes);
dstDoc.save("output.doc");

Extrahera text mellan olika noder av Word-dokument

Du kan även extrahera innehåll mellan olika typer av noder. För demonstration, låt oss extrahera innehåll mellan ett stycke och en tabell och spara det i ett nytt Word-dokument. Följande är stegen för att extrahera text mellan olika noder i ett Word-dokument i Java.

  • Ladda Word-dokumentet med dokumentklassen.
  • Hämta referens för start- och slutnoderna till två objekt med metoden Document.getFirstSection().getChild(NodeType, int, bool).
  • Anropa metoden extractContent(startPara, endPara, true) för att extrahera noderna till ett objekt.
  • Anrop genereraDocument(Document, extractedNodes) hjälparmetod för att skapa dokument som består av det extraherade innehållet.
  • Spara det returnerade dokumentet med metoden Document.save(String).

Följande kodexempel visar hur man extraherar text mellan ett stycke och en tabell i ett Word DOCX med Java.

// Ladda dokument
Document doc = new Document("TestFile.doc");

// Få referens till startstycket
Paragraph startPara = (Paragraph) doc.getLastSection().getChild(NodeType.PARAGRAPH, 2, true);
Table endTable = (Table) doc.getLastSection().getChild(NodeType.TABLE, 0, true);

// Extrahera innehållet mellan dessa noder i dokumentet. Inkludera dessa markörer i extraktionen.
ArrayList extractedNodes = extractContent(startPara, endTable, true);

// Låter vända arrayen för att göra det lättare att infoga innehållet tillbaka i dokumentet.
Collections.reverse(extractedNodes);

while (extractedNodes.size() > 0) {
    // Infoga den sista noden från den omvända listan
    endTable.getParentNode().insertAfter((Node) extractedNodes.get(0), endTable);
    // Ta bort denna nod från listan efter infogning.
    extractedNodes.remove(0);
}

// Spara det genererade dokumentet på disken.
doc.save("output.doc");

Få text mellan stycken baserat på stilar

Låt oss nu kolla in hur man extraherar innehåll mellan stycken baserat på stilar. För demonstration kommer vi att extrahera innehåll mellan den första “Rubrik 1” och den första “Rubrik 3” i Word-dokumentet. Följande steg visar hur man uppnår detta i Java.

  • Ladda först Word-dokumentet med Document class.
  • Extrahera sedan stycken till ett objekt med hjälpmetoden paragraphsByStyleName(Document, “Rubrik 1”).
  • Extrahera stycken till ett annat objekt med hjälpmetoden paragraphsByStyleName(Document, “Rubrik 3”).
  • Anropa metoden extractContent(startPara, endPara, true) och skicka de första elementen i båda paragrafmatriserna som första och andra parametrar.
  • Anrop genereraDocument(Document, extractedNodes) hjälparmetod för att skapa dokument som består av det extraherade innehållet.
  • Slutligen sparar du det returnerade dokumentet med metoden Document.save(String).

Följande kodexempel visar hur man extraherar innehåll mellan stycken baserat på stilar.

// Ladda dokument
Document doc = new Document(dataDir + "TestFile.doc");

// Samla en lista över styckena med respektive rubrikstil.
ArrayList parasStyleHeading1 = paragraphsByStyleName(doc, "Heading 1");
ArrayList parasStyleHeading3 = paragraphsByStyleName(doc, "Heading 3");

// Använd den första instansen av styckena med dessa formatmallar.
Node startPara1 = (Node) parasStyleHeading1.get(0);
Node endPara1 = (Node) parasStyleHeading3.get(0);

// Extrahera innehållet mellan dessa noder i dokumentet. Inkludera inte dessa markörer i extraktionen.
ArrayList extractedNodes = extractContent(startPara1, endPara1, false);

// Infoga innehållet i ett nytt separat dokument och spara det på disk.
Document dstDoc = generateDocument(doc, extractedNodes);
dstDoc.save("output.doc");

Gratis Word Text Extractor för Java

Du kan få en gratis tillfällig licens för att extrahera text från Word-dokument utan utvärderingsbegränsningar.

Utforska Java DOCX Library

Du kan utforska andra scenarier för att extrahera text från Word-dokument med hjälp av denna dokumentationsartikeln. Dessutom kan du utforska andra funktioner i Aspose.Words för Java med hjälp av dokumentationen. Om du har några frågor är du välkommen att meddela oss via vårt forum.

Slutsats

I den här artikeln har du lärt dig hur du extraherar text från Word-dokument i Java. Dessutom har du sett hur man extraherar innehåll mellan liknande eller olika typer av noder i en DOC eller DOCX programmatiskt. Således kan du bygga din egen MS Word-textextraktor i Java.

Se även