JavaとApache POIを使いWordドキュメント(docx形式/OOXML形式)のブックマークを操作する

月曜日 , 30, 10月 2023 Leave a comment

Apache POIを使いWord文書(docx形式/OOXML形式)のブックマークを操作を扱ったサンプルプログラムが少なかったので、サンプルプログラムを書いてみました。

サンプルプログラムでは、Wordドキュメントに含まれるブックマークの一覧を取得し、ブックマークが指し示す範囲のテキストを、ブックマーク名と一緒に表示します。

実行結果として、範囲内に含まれるテキストのみを表示していますが、実際の処理ではOOXMLのw:bookmarkStartからw:bookmarkEndまでのすべて.の要素を取得しているので、w:rなどに対し装飾を操作することで見た目の操作を行うことも可能になります。

  • サンプルプログラムの実行例

スクリーンショット 2023-10-30 210609

  • サンプルプログラムに投入したWord文書の一部

文章中の一部のみにブックマークを設定。そのまま抽出されていることが確認できます

import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.List;

import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFTable;
import org.apache.poi.xwpf.usermodel.XWPFTableCell;
import org.apache.poi.xwpf.usermodel.XWPFTableRow;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTBookmark;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 *	Wordドキュメントに含まれるブックマークが指し示す範囲のテキストを一覧表示するプログラム
 *
 *	This software is distributed under the license of NYSL.<br>
 *	<a href="http://www.kmonos.net/nysl/">http://www.kmonos.net/nysl/</a>
 * 
 * @author makoto
 *	
 */
public class BookmarkList {

	/** OOXMLのRunクラス */
	private static final String RUN_NODE_NAME = "w:r";
	/** OOXMLのTextクラス */
	private static final String TEXT_NODE_NAME = "w:t";
	/** OOXMLのBookmarkStartクラス */
	private static final String BOOKMARK_START_TAG = "w:bookmarkStart";
	/** OOXMLのBookmarkEndクラス */
	private static final String BOOKMARK_END_TAG = "w:bookmarkEnd";
	/** OOXMLのRunPropertiesクラス */
	private static final String STYLE_NODE_NAME = "w:rPr";
	/** ID */
	private static final String ID_ATTR_NAME = "w:id";

	/**
	 * 
	 * @param args
	 */
	public static void main(String[] args) {

		try {
			
			if (args.length == 0) {
				System.out.println("[使い方]");
				System.out.println("java BookmarkList Wordドキュメント名");
				return;
			}
			
			XWPFDocument document = new XWPFDocument(new FileInputStream(args[0]));

			// ドキュメントに含まれるブックマークを探し
			List<CTBookmark>bookmarks = findBookmarks(document);
			
			// ブックマーク名とブックマークに含まれるテキストを表示する
			for (CTBookmark bookmark: bookmarks) {
				System.out.println(bookmark.getName() + " => " + getText(collectNodes(bookmark)));
			}

		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * 	Wordドキュメントに含まれるブックマークを探し返す
	 * 
	 * @param document
	 * @return
	 */
	private static List<CTBookmark> findBookmarks(XWPFDocument document) {

		List<CTBookmark>bookmarks = new ArrayList<>();
		
		// 段落
		List<XWPFParagraph> paragraphs = document.getParagraphs();
		for (XWPFParagraph paragraph : paragraphs) {
			bookmarks.addAll(paragraph.getCTP().getBookmarkStartList());
		}
		
		// 表
		List<XWPFTable> tables = document.getTables();
		for (XWPFTable table : tables) {
			bookmarks.addAll(findBookmarks(table));
		}
		
		return bookmarks;
	}

	/**
	 * 	Wordドキュメントの表に含まれるブックマークを探す
	 * 
	 * @param table
	 * @return
	 */
	private static List<CTBookmark> findBookmarks(XWPFTable table) {

		List<CTBookmark>bookmarks = new ArrayList<>();
		
		bookmarks.addAll(table.getCTTbl().getBookmarkStartList());

		for (XWPFTableRow row : table.getRows()) {
			bookmarks.addAll(findBookmarks(row));
		}
		
		return bookmarks;
	}

	/**
	 * 	Wordドキュメントの表の行に含まれるブックマークを探す
	 * 
	 * @param row
	 * @return
	 */
	private static List<CTBookmark> findBookmarks(XWPFTableRow row) {

		List<CTBookmark>bookmarks = new ArrayList<>();
		
		bookmarks.addAll(row.getCtRow().getBookmarkStartList());

		for (XWPFTableCell cell : row.getTableCells()) {
			bookmarks.addAll(cell.getCTTc().getBookmarkStartList());
			for (XWPFParagraph paragraph : cell.getParagraphs()) {
				bookmarks.addAll(paragraph.getCTP().getBookmarkStartList());
			}
		}
		
		return bookmarks;
	}

	/**
	 * 	ブックマークの範囲に含まれるノードを集める
	 * 
	 * @param bookmark
	 * @return
	 */
	private static List<Node> collectNodes(CTBookmark bookmark) {
		
		List<Node>nodes = new ArrayList<>();
		
		int startId = bookmark.getId().intValue();
		int endId = -1;
	
		Node nextNode = bookmark.getDomNode();
	
		// bookmarkStartのIDと同じIDを持つbookmarkEndが出現するまでループ
		while (startId != endId) {
			
			nextNode = nextNode.getNextSibling();
			if (nextNode == null) {
				// w:bookmarkEndが出現しないまま、終端まで到達したときの対策
				break;
			}
	
			if (nextNode.getNodeName().startsWith(BOOKMARK_END_TAG)) {
				try {
					endId = Integer.parseInt(nextNode.getAttributes().getNamedItem(ID_ATTR_NAME).getNodeValue());
				} catch (NumberFormatException e) {
					endId = startId;
				}
			} else {
				if (nextNode.getNodeName().startsWith(RUN_NODE_NAME)) {
					nodes.add(nextNode);
					nodes.addAll(collectNodes(nextNode.getChildNodes()));
				}
			}
		}
		
		return nodes;
	}

	/**
	 * 	ノードを集める
	 * 
	 * @param nodeList
	 * @return
	 */
	private static List<Node> collectNodes(NodeList nodeList) {
	
		List<Node>nodes = new ArrayList<>();
		
		for (int i = 0; i < nodeList.getLength(); i++) {
			nodes.addAll(collectNodes(nodeList.item(i)));
		}
		
		return nodes;
	}

	/**
	 * 	ノードを集める
	 * 
	 * @param node
	 * @return
	 */
	private static List<Node> collectNodes(Node node) {
		
		List<Node>nodes = new ArrayList<>();
		
		nodes.add(node);
		nodes.addAll(collectNodes(node.getChildNodes()));
		
		return nodes;
	}

	/**
	 * 	ノード群からテキストを取得する
	 * 
	 * @param nodes
	 * @return
	 */
	private static String getText(List<Node>nodes) {
		
		StringBuffer sb = new StringBuffer();
		
		for (Node node:nodes) {
			if (node.getNodeValue() == null) { 
				continue; 
			}
			sb.append(node.getNodeValue());
		}
		
		return sb.toString();
	}
}

    Tags:, , ,

    Please give us your valuable comment

    メールアドレスが公開されることはありません。 が付いている欄は必須項目です

    このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください