This is very much a hack but I hope it will get you working for now. There are a couple of points to bear in mind, areas that will be fixed shortly I hope.
The first is that the code dealing with bookmarked table cells only supports the 'replace' option at the moment. This is to say that any and all text found in the table cell will be deleted before the new value is written into it. This has an impact on nested bookmakrs as they will also be removed from the cell. Currently, therefore, it is not possible to have a bookmarked cell that contains bookmarked text and update both. Finally, the current code does not take styling into account. I have not yet tried applying a style to a table cell - it should be possible of course - and then using this to affect the style of it's contents. This should be an easy fix thought and I will post it soon - hopefully. The code has also changed somewhat because I wanted to modularise it. There are now two classes, the Bookmarks class which is simply a container for instances of the Bookmark class. Here is the source for both (it is still in development and far from fully finished so forgive the rough edges please); 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 java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Collection; import java.util.Set; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; /** * * @author Mark Beardsley */ public class Bookmarks { private HashMap<String, Bookmark> _bookmarks = null; /** * Create an instance of the XWPFBookmakrs class using the following * parameters. * * @param document An instance of the XWPFDocument class encapsulating * information about a Word OOXML document. */ protected Bookmarks(XWPFDocument document) { List<XWPFTable> tableList = null; Iterator<XWPFTable> tableIter = null; List<XWPFTableRow> rowList = null; Iterator<XWPFTableRow> rowIter = null; List<XWPFTableCell> cellList = null; Iterator<XWPFTableCell> cellIter = null; XWPFTable table = null; XWPFTableRow row = null; XWPFTableCell cell = null; this._bookmarks = new HashMap<String, Bookmark>(); // Firstly, deal with any paragraphs in the body of the document. this.procParaList(document.getParagraphs()); // Then check to see if there are any bookmarks in table cells. To do this // it is necessary to get at the list of paragraphs 'stored' within the // individual table cell, hence this code which get the tables from the // document, the rows from each table, the cells from each row and the // paragraphs from each cell. tableList = document.getTables(); tableIter = tableList.iterator(); while (tableIter.hasNext()) { table = tableIter.next(); rowList = table.getRows(); rowIter = rowList.iterator(); while (rowIter.hasNext()) { row = rowIter.next(); cellList = row.getTableCells(); cellIter = cellList.iterator(); while (cellIter.hasNext()) { cell = cellIter.next(); this.procParaList(cell.getParagraphs(), row); } } } } /** * Get a reference to a specific bookmark. * * @param bookmarkName An instance of the String class that encapsulates the * name of the bookmark. * @return An instance of the Bookmark class that will encapsulate a * reference to a named bookmark if it exists within the document, null * otherwise. */ public Bookmark getBookmark(String bookmarkName) { Bookmark bookmark = null; if(this._bookmarks.containsKey(bookmarkName)) { bookmark = this._bookmarks.get(bookmarkName); } return(bookmark); } /** * Get all of the bookmarks in the document. * * @return An instance of a class that implements the Collections interface * and which makes it possible to access all of the bookmarks within the * document. */ public Collection<Bookmark> getBookmarkList() { return(this._bookmarks.values()); } /** * Gat an instance of the Iterator class that encapsulates the names * of all of the bookmarks. * * @return An instance of the Iterator class that encapsulates references to * one or more String(s). Each String will itself encapsulate the name of a * bookmark. */ public Iterator<String> getNameIterator() { return(this._bookmarks.keySet().iterator()); } /** * Get the names of all of the bookmarks in the document. * * @return An instance of a class that implements the Set interface and * which contains the names of the bookmarks in this document. Note that * this assumes it is not possible to have more that one bookmark with the * same name in the same document. */ private Set<String> getBookmarkNameSet() { return(this._bookmarks.keySet()); } private void procParaList(List<XWPFParagraph> paraList, XWPFTableRow tableRow) { Iterator<XWPFParagraph> paraIter = null; XWPFParagraph para = null; List<CTBookmark> bookmarkList = null; Iterator<CTBookmark> bookmarkIter = null; CTBookmark bookmark = null; NamedNodeMap attributes = null; Node colFirstNode = null; Node colLastNode = null; int firstColIndex = 0; int lastColIndex = 0; // Step through the list of pargraphs and check whether or not each // one contains a bookmark. If it does, create an instance of the // Bookmark class encapsulating the bookmark/para pairing and // write away to the list paraIter = paraList.iterator(); while(paraIter.hasNext()) { para = paraIter.next(); bookmarkList = para.getCTP().getBookmarkStartList(); bookmarkIter = bookmarkList.iterator(); while (bookmarkIter.hasNext()) { bookmark = bookmarkIter.next(); // With a bookmark in hand, test to see if the bookmarkStart tag // has w:colFirst or w:colLast attributes. If it does, we are // dealing with a bookmarked table cell. This will need to be // handled differnetly - I think by an different concrete class // that implements the Bookmark interface!! attributes = bookmark.getDomNode().getAttributes(); if(attributes != null) { // Get the colFirst and colLast attributes. If both - for // now - are found, then we are dealing with a bookmarked // cell. colFirstNode = attributes.getNamedItem("w:colFirst"); colLastNode = attributes.getNamedItem("w:colLast"); if(colFirstNode != null && colLastNode != null) { // Get the index of the cell (or cells later) from them. // First convefrt the String values both return to primitive // int value. TO DO, what happens if there is a // NumberFormatException. firstColIndex = Integer.parseInt(colFirstNode.getNodeValue()); lastColIndex = Integer.parseInt(colLastNode.getNodeValue()); // if the indices are equal, then we are dealing with a# // cell and can create the bookmark for it. if(firstColIndex == lastColIndex) { this._bookmarks.put(bookmark.getName(), new Bookmark(bookmark, para, tableRow.getCell(firstColIndex))); } else { System.out.println("This bookmark " + bookmark.getName() + " identifies a number of cells in the " + "table. That condition is not handled yet."); } } else { this._bookmarks.put(bookmark.getName(), new Bookmark(bookmark, para)); } } else { this._bookmarks.put(bookmark.getName(), new Bookmark(bookmark, para)); } } } } private void procParaList(List<XWPFParagraph> paraList) { Iterator<XWPFParagraph> paraIter = null; XWPFParagraph para = null; List<CTBookmark> bookmarkList = null; Iterator<CTBookmark> bookmarkIter = null; CTBookmark bookmark = null; // Step through the list of pargraphs and check whether or not each // one contains a bookmark. If it does, create an instance of the // Bookmark class encapsulating the bookmark/para pairing and // write away to the list paraIter = paraList.iterator(); while(paraIter.hasNext()) { para = paraIter.next(); bookmarkList = para.getCTP().getBookmarkStartList(); bookmarkIter = bookmarkList.iterator(); while (bookmarkIter.hasNext()) { bookmark = bookmarkIter.next(); this._bookmarks.put(bookmark.getName(), new Bookmark(bookmark, para)); } } } } import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTBookmark; import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTText; import org.apache.xmlbeans.XmlException; import org.apache.poi.xwpf.usermodel.XWPFParagraph; import org.apache.poi.xwpf.usermodel.XWPFRun; import org.apache.poi.xwpf.usermodel.XWPFTableCell; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import java.util.Stack; import java.util.List; /** * * @author Mark Beardsley */ public class Bookmark { public static final int INSERT_AFTER = 0; public static final int INSERT_BEFORE = 1; public static final int REPLACE = 2; private static final String RUN_NODE_NAME = "w:r"; private static final String TEXT_NODE_NAME = "w:t"; private static final String BOOKMARK_START_TAG = "bookmarkStart"; private static final String BOOKMARK_END_TAG = "bookmarkEnd"; private static final String BOOKMARK_ID_ATTR_NAME = "w:id"; private static final String STYLE_NODE_NAME = "w:rPr"; private CTBookmark _ctBookmark = null; private XWPFParagraph _para = null; private XWPFTableCell _tableCell = null; private String _bookmarkName = null; private boolean _isCell = false; /** * Create an instance of the Bookmark class using the following * parameters. * * @param ctBookmark An instance of the CTBookmark class that encapsulates * information about a bookmark within a Word document. * @param para An instance of the XWPFParagraph class that encapsulates * information about a paragraph of text within a Word document. The * reference passed to this parameter will be for the paragraph that contains * the bookmark. */ protected Bookmark(CTBookmark ctBookmark, XWPFParagraph para) { this._ctBookmark = ctBookmark; this._para = para; this._bookmarkName = ctBookmark.getName(); this._tableCell = null; this._isCell = false; } protected Bookmark(CTBookmark ctBookmark, XWPFParagraph para, XWPFTableCell tableCell) { this(ctBookmark, para); this._tableCell = tableCell; this._isCell = true; } protected Bookmark(CTBookmark ctBookmark, XWPFParagraph para, List<XWPFTableCell> tableCellList) { // Adding this constructor in, just in case it is possible to // bookmark two or more cells in a table. } /** * Get the name of the bookmark. * * @return An instance of the String class that encapsulates the name of the * bookmark as seen in the Insert->Bookmark dialog box. */ public String getBookmarkName() { return(this._bookmarkName); } /** * Insert text into the Word document in the location indicated by this * bookmark. * * @param bookmarkValue An instance of the String class that encapsulates * the text to insert into the document. * @param where A primitive int whose value indicates where the text ought * to be inserted. There are three options controlled by constants; insert * the text immediately in front of the bookmark (Bookmark.INSERT_BEFORE), * insert text immediately after the bookmark (Bookmark.INSERT_AFTER) and * replace any and all text that appears between the bookmark's square * brackets (Bookmark.REPLACE). */ public void insertTextAtBookmark(String bookmarkValue, int where) { XWPFRun run = null; // Are we dealing with a bookmarked table cell? if(this._isCell) { this.handleBookmarkedCells(bookmarkValue, where); } else { // We are not dealing with a table cell so, create the run for the // paragraph then check how it ought to be handled. run = this._para.createRun(); run.setText(bookmarkValue); switch(where) { case Bookmark.INSERT_AFTER: this.insertAfterBookmark(run); break; case Bookmark.INSERT_BEFORE: this.insertBeforeBookmark(run); break; case Bookmark.REPLACE: this.replaceBookmark(run); break; } } } /** * Inserts some text into a Word document in a position that is immediately * after a named bookmark. * * Bookmarks can take two forms, they can either simply mark a location * within a document or they can do this but contain some text. The * difference is obvious from looking at some XML markup. The simple * placeholder bookmark will look like this; * * <pre> * * <w:bookmarkStart w:name="AllAlone" w:id="0"/><w:bookmarkEnd w:id="0"/> * * </pre> * * Simply a pair of tags where one tag has the name bookmarkStart, the other * the name bookmarkEnd and both share matching id attributes. In this case, * the text will simply be inserted into the document at a point immediately * after the bookmarkEnd tag. No styling will be applied to the text, it * will simply inherit the documents defaults. * * The more complex case looks like this; * * <pre> * * <w:bookmarkStart w:name="InStyledText" w:id="3"/> * <w:r w:rsidRPr="00DA438C"> * <w:rPr> * <w:rFonts w:hAnsi="Engravers MT" w:ascii="Engravers MT" w:cs="Arimo"/> * <w:color w:val="FF0000"/> * </w:rPr> * <w:t>text</w:t> * </w:r> * <w:bookmarkEnd w:id="3"/> * * </pre> * * Here, the user has selected the word 'text' and chosen to insert a * bookmark into the document at that point. So, the bookmark tags 'contain' * a character run that is styled. Inserting any text after this bookmark, * it is important to ensure that the styling is preserved and copied over * to the newly inserted text. * * The approach taken to dealing with both cases is similar but slightly * different. In both cases, the code simply steps along the document nodes * until it finds the bookmarkEnd tag whose ID matches that of the * bookmarkStart tag. Then, it will look to see if there is one further node * following the bookmarkEnd tag. If there is, it will insert the text into * the paragraph immediately in front of this node. If, on the other hand, * there are no more nodes following the bookmarkEnd tag, then the new run * will simply be positioned at the end of the paragraph. * * Styles are dealt with by 'looking' for a 'w:rPr' element whilst iterating * through the nodes. If one is found, its details will be captured and * applied to the run before the run is inserted into the paragraph. If * there are multiple runs between the bookmarkStart and bookmarkEnd tags * and these have different styles applied to them, then the style applied * to the last run before the bookmarkEnd tag - if any - will be cloned and * applied to the newly inserted text. * * @param run An instance of the XWPFRun class that encapsulates the text * that is to be inserted into the document following the bookmark. */ private void insertAfterBookmark(XWPFRun run) { Node nextNode = null; Node insertBeforeNode = null; Node styleNode = null; int bookmarkStartID = 0; int bookmarkEndID = -1; // Capture the id of the bookmarkStart tag. The code will step through // the document nodes 'contained' within the start and end tags that have // matching id numbers. bookmarkStartID = this._ctBookmark.getId().intValue(); // Get the node for the bookmark start tag and then enter a loop that // will step from one node to the next until the bookmarkEnd tag with // a matching id is fouind. nextNode = this._ctBookmark.getDomNode(); while (bookmarkStartID != bookmarkEndID) { // Get the next node along and check to see if it is a bookmarkEnd // tag. If it is, get its id so that the containing while loop can // be terminated once the correct end tag is found. Note that the // id will be obtained as a String and must be converted into an // integer. This has been coded to fail safely so that if an error // is encuntered converting the id to an int value, the while loop // will still terminate. nextNode = nextNode.getNextSibling(); if (nextNode.getNodeName().contains(Bookmark.BOOKMARK_END_TAG)) { try { bookmarkEndID = Integer.parseInt( nextNode.getAttributes().getNamedItem( Bookmark.BOOKMARK_ID_ATTR_NAME).getNodeValue()); } catch (NumberFormatException nfe) { bookmarkEndID = bookmarkStartID; } } // If we are not dealing with a bookmarkEnd node, are we dealing // with a run node that MAY contains styling information. If so, // then get that style information from the run. else { if (nextNode.getNodeName().equals(Bookmark.RUN_NODE_NAME)) { styleNode = this.getStyleNode(nextNode); } } } // After the while loop completes, it should have located the correct // bookmarkEnd tag but we cannot perform an insert after only an insert // before operation and must, therefore, get the next node. insertBeforeNode = nextNode.getNextSibling(); // Style the newly inserted text. Note that the code copies or clones // the style it found in another run, failure to do this would remove the // style from one node and apply it to another. if (styleNode != null) { run.getCTR().getDomNode().insertBefore( styleNode.cloneNode(true), run.getCTR().getDomNode().getFirstChild()); } // Finally, check to see if there was a node after the bookmarkEnd // tag. If there was, then this code will insert the run in front of // that tag. If there was no node following the bookmarkEnd tag then the // run will be inserted at the end of the paragarph and this was taken // care of at the point of creation. if (insertBeforeNode != null) { this._para.getCTP().getDomNode().insertBefore( run.getCTR().getDomNode(), insertBeforeNode); } } /** * Inserts some text into a Word document immediately in front of the * location of a bookmark. * * This case is slightly more straightforward than inserting after the * bookmark. For example, it is possible only to insert a new node in front * of an existing node. When inserting after the bookmark, then end node had * to be located whereas, in this case, the node is already known, it is the * CTBookmark itself. The only information that must be discovered is * whether there is a run immediately in front of the boookmarkStart tag and * whether that run is styled. If there is and if it is, then this style * must be cloned and applied the text which will be inserted into the * paragraph. * * @param run An instance of the XWPFRun class that encapsulates the text * that is to be inserted into the document following the bookmark. */ private void insertBeforeBookmark(XWPFRun run) { Node insertBeforeNode = null; Node childNode = null; Node styleNode = null; // Get the dom node from the bookmarkStart tag and look for another // node immediately preceding it. insertBeforeNode = this._ctBookmark.getDomNode(); childNode = insertBeforeNode.getPreviousSibling(); // If a node is found, try to get the styling from it. if (childNode != null) { styleNode = this.getStyleNode(childNode); // If that previous node was styled, then apply this style to the // text which will be inserted. if (styleNode != null) { run.getCTR().getDomNode().insertBefore( styleNode.cloneNode(true), run.getCTR().getDomNode().getFirstChild()); } } // Insert the text into the paragraph immediately in front of the // bookmarkStart tag. this._para.getCTP().getDomNode().insertBefore( run.getCTR().getDomNode(), insertBeforeNode); } /** * Replace the text - if any - contained between the bookmarkStart and it's * matching bookmarkEnd tag with the text specified. The technique used will * resemble that employed when inserting text after the bookmark. In short, * the code will iterate along the nodes until it encounters a matching * bookmarkEnd tag. Each node encountered will be deleted unless it is the * final node before the bookmarkEnd tag is encountered and it is a * character run. If this is the case, then it can simply be updated to * contain the text the users wishes to see inserted into the document. If * the last node is not a character run, then it will be deleted, a new run * will be created and inserted into the paragraph between the bookmarkStart * and bookmarkEnd tags. * * @param run An instance of the XWPFRun class that encapsulates the text * that is to be inserted into the document following the bookmark. */ private void replaceBookmark(XWPFRun run) { Node nextNode = null; Node styleNode = null; Node lastRunNode = null; Node toDelete = null; NodeList childNodes = null; Stack<Node> nodeStack = null; boolean textNodeFound = false; boolean foundNested = true; int bookmarkStartID = 0; int bookmarkEndID = -1; int numChildNodes = 0; nodeStack = new Stack<Node>(); bookmarkStartID = this._ctBookmark.getId().intValue(); nextNode = this._ctBookmark.getDomNode(); nodeStack.push(nextNode); // Loop through the nodes looking for a matching bookmarkEnd tag while (bookmarkStartID != bookmarkEndID) { nextNode = nextNode.getNextSibling(); nodeStack.push(nextNode); // If an end tag is found, does it match the start tag? If so, end // the while loop. if (nextNode.getNodeName().contains(Bookmark.BOOKMARK_END_TAG)) { try { bookmarkEndID = Integer.parseInt( nextNode.getAttributes().getNamedItem( Bookmark.BOOKMARK_ID_ATTR_NAME).getNodeValue()); } catch (NumberFormatException nfe) { bookmarkEndID = bookmarkStartID; } } //else { // Place a reference to the node on the nodeStack // nodeStack.push(nextNode); //} } // If the stack of nodes found between the bookmark tags is not empty // then they have to be removed. if (!nodeStack.isEmpty()) { // Check the node at the top of the stack. If it is a run, get it's // style - if any - and apply to the run that will be replacing it. //lastRunNode = nodeStack.pop(); lastRunNode = nodeStack.peek(); if ((lastRunNode.getNodeName().equals(Bookmark.RUN_NODE_NAME))) { styleNode = this.getStyleNode(lastRunNode); if (styleNode != null) { run.getCTR().getDomNode().insertBefore( styleNode.cloneNode(true), run.getCTR().getDomNode().getFirstChild()); } } // Delete any and all node that were found in between the start and // end tags. This is slightly safer that trying to delete the nodes // as they are found while stepping through them in the loop above. // If we are peeking, then this line can be commented out. //this._para.getCTP().getDomNode().removeChild(lastRunNode); this.deleteChildNodes(nodeStack); } // Place the text into position, between the bookmark tags. this._para.getCTP().getDomNode().insertBefore( run.getCTR().getDomNode(), nextNode); } /** * When replacing the bookmark's text, it is necessary to delete any nodes * that are found between matching start and end tags. Complications occur * here because it is possible to have bookmarks nested within bookmarks to * almost any level and it is important to not remove any inner or nested * bookmarks when replacing the contents of an outer or containing * bookmark. This code successfully handles the simplest occurrence - where * one bookmark completely contains another - but not more complex cases * where one bookmark overlaps another in the markup. That is still to do. * * @param nodeStack An instance of the Stack class that encapsulates * references to any and all nodes found between the opening and closing * tags of a bookmark. */ private void deleteChildNodes(Stack<Node> nodeStack) { Node toDelete = null; int bookmarkStartID = 0; int bookmarkEndID = 0; boolean inNestedBookmark = false; // The first element in the list will be a bookmarkStart tag and that // must not be deleted. for(int i = 1; i < nodeStack.size(); i++) { // Get an element. If it is another bookmarkStart tag then // again, we do not want to delete it, it's matching end tag // or any nodes that fall inbetween. toDelete = nodeStack.elementAt(i); if(toDelete.getNodeName().contains(Bookmark.BOOKMARK_START_TAG)) { bookmarkStartID = Integer.parseInt( toDelete.getAttributes().getNamedItem(Bookmark.BOOKMARK_ID_ATTR_NAME).getNodeValue()); inNestedBookmark = true; } else if(toDelete.getNodeName().contains(Bookmark.BOOKMARK_END_TAG)) { bookmarkEndID = Integer.parseInt( toDelete.getAttributes().getNamedItem(Bookmark.BOOKMARK_ID_ATTR_NAME).getNodeValue()); if(bookmarkEndID == bookmarkStartID) { inNestedBookmark = false; } } else { if(!inNestedBookmark) { this._para.getCTP().getDomNode().removeChild(toDelete); } } } } /** * Recover styling information - if any - from another document node. Note * that it is only possible to accomplish this if the node is a run (w:r) * and this could be tested for in the code that calls this method. However, * a check is made in the calling code as to whether a style has been found * and only if a style is found is it applied. This method always returns * null if it does not find a style making that checking process easier. * * @param parentNode An instance of the Node class that encapsulates a * reference to a document node. * @return An instance of the Node class that encapsulates the styling * information applied to a character run. Note that if no styling * information is found in the run OR if the node passed as an argument to * the parentNode parameter is NOT a run, then a null value will be * returned. */ private Node getStyleNode(Node parentNode) { Node childNode = null; Node styleNode = null; if (parentNode != null) { // If the node represents a run and it has child nodes then // it can be processed further. Note, whilst testing the code, it // was observed that although it is possible to get a list of a nodes // children, even when a node did have children, trying to obtain this // list would often return a null value. This is the reason why the // technique of stepping from one node to the next is used here. if (parentNode.getNodeName().equalsIgnoreCase(Bookmark.RUN_NODE_NAME) && parentNode.hasChildNodes()) { // Get the first node and catch it's reference for return if // the first child node is a style node (w:rPr). childNode = parentNode.getFirstChild(); if (childNode.getNodeName().equals("w:rPr")) { styleNode = childNode; } else { // If the first node was not a style node and there are other // child nodes remaining to be checked, then step through // the remaining child nodes until either a style node is // found or until all child nodes have been processed. while ((childNode = childNode.getNextSibling()) != null) { if (childNode.getNodeName().equals(Bookmark.STYLE_NODE_NAME)) { styleNode = childNode; // Note setting to null here if a style node is // found in order order to terminate any further // checking childNode = null; } } } } } return (styleNode); } /** * Get the text - if any - encapsulated by this bookmark. The creator of a * Word document can chose to select one or more items of text and then * insert a bookmark at that location. The highlighted text will appear * between the square brackets that denote the location of a bookmark in the * document's text and they will be returned by a call to this method. * * @return An instance of the String class encapsulating any text that * appeared between the opening and closing square bracket associated with * this bookmark. * @throws XmlException Thrown if a problem is encountered parsing the XML * markup recovered from the document in order to construct a CTText * instance which may required to obtain the bookmarks text. */ public String getBookmarkText() throws XmlException { StringBuilder builder = null; // Are we dealing with a bookmarked table cell? If so, the entire // contents of the cell - if anything - must be recovered and returned. if(this._tableCell != null) { builder = new StringBuilder(this._tableCell.getText()); } else { builder = this.getTextFromBookmark(); } return(builder == null ? null : builder.toString()); } /** * There are two types of bookmarks. One is a simple placeholder whilst the * second is still a placeholder but it 'contains' some text. In the second * instance, the creator of the document has selected some text and then * chosen to insert a bookmark there and the difference if obvious when * looking at the XML markup. * * The simple case; * * <pre> * * <w:bookmarkStart w:name="AllAlone" w:id="0"/><w:bookmarkEnd w:id="0"/> * * </pre> * * The more complex case; * * <pre> * * <w:bookmarkStart w:name="InStyledText" w:id="3"/> * <w:r w:rsidRPr="00DA438C"> * <w:rPr> * <w:rFonts w:hAnsi="Engravers MT" w:ascii="Engravers MT" w:cs="Arimo"/> * <w:color w:val="FF0000"/> * </w:rPr> * <w:t>text</w:t> * </w:r> * <w:bookmarkEnd w:id="3"/> * * </pre> * * This method assumes that the user wishes to recover the content from any * character run that appears in the markup between a matching pair of * bookmarkStart and bookmarkEnd tags; thus, using the example above again, * this method would return the String 'text' to the user. It is possible * however for a bookmark to contain more than one run and for a bookmark to * contain other bookmarks. In both of these cases, this code will return * the text contained within any and all runs that appear in the XML markup * between matching bookmarkStart and bookmarkEnd tags. The term 'matching * bookmarkStart and bookmarkEndtags' here means tags whose id attributes * have matching value. * * @return An instance of the StringBuilder class encapsulating the text * recovered from any character run elements found between the bookmark's * start and end tags. If no text is found then a null value will be * returned. * @throws XmlException Thrown if a problem is encountered parsing the XML * markup recovered from the document in order to construct a CTText * instance which may be required to obtain the bookmarks text. */ private StringBuilder getTextFromBookmark() throws XmlException { int startBookmarkID = 0; int endBookmarkID = -1; Node nextNode = null; Node childNode = null; CTText text = null; StringBuilder builder = null; String rawXML = null; // Get the ID of the bookmark from it's start tag, the DOM node from the // bookmark (to make looping easier) and initialise the StringBuilder. startBookmarkID = this._ctBookmark.getId().intValue(); nextNode = this._ctBookmark.getDomNode(); builder = new StringBuilder(); // Loop through the nodes held between the bookmark's start and end // tags. while (startBookmarkID != endBookmarkID) { // Get the next node and, if it is a bookmarkEnd tag, get it's ID // as matching ids will terminate the while loop.. nextNode = nextNode.getNextSibling(); if (nextNode.getNodeName().contains(Bookmark.BOOKMARK_END_TAG)) { // Get the ID attribute from the node. It is a String that must // be converted into an int. An exception could be thrown and so // the catch clause will ensure the loop ends neatly even if the // value might be incorrect. Must inform the user. try { endBookmarkID = Integer.parseInt( nextNode.getAttributes(). getNamedItem(Bookmark.BOOKMARK_ID_ATTR_NAME).getNodeValue()); } catch (NumberFormatException nfe) { endBookmarkID = startBookmarkID; } } else { // This is not a bookmarkEnd node and can processed it for any // text it may contain. Note the check for both type - it must // be a run - and contain children. Interestingly, it seems as // though the node may contain children and yet the call to // nextNode.getChildNodes() will still return an empty list, // hence the need to step through the child nodes. if (nextNode.getNodeName().equals(Bookmark.RUN_NODE_NAME) && nextNode.hasChildNodes()) { // Get the text from the child nodes. builder.append(this.getTextFromChildNodes(nextNode)); } } } return (builder); } /** * Iterates through all and any children of the Node whose reference will be * passed as an argument to the node parameter, and recover the contents of * any text nodes. Testing revealed that a node can be called a text node * and yet report it's type as being something different, an element node * for example. Calling the getNodeValue() method on a text node will return * the text the node encapsulates but doing the same on an element node will * not. In fact, the call will simply return a null value. As a result, this * method will test the nodes name to catch all text nodes - those whose * name is to 'w:t' and then it's type. If the type is reported to be a text * node, it is a trivial task to get at it's contents. However, if the type * is not reported as a text type, then it is necessary to parse the raw XML * markup for the node to recover it's value. * * @param node An instance of the Node class that encapsulates a reference * to a node recovered from the document being processed. It should be * passed a reference to a character run - 'w:r' - node. * @return An instance of the String class that encapsulates the text * recovered from the nodes children, if they are text nodes. * @throws XmlException Thrown if a problem is encountered parsing the XML * markup recovered from the document in order to construct the CTText * instance which may be required to obtain the bookmarks text. */ private String getTextFromChildNodes(Node node) throws XmlException { NodeList childNodes = null; Node childNode = null; CTText text = null; StringBuilder builder = new StringBuilder(); int numChildNodes = 0; // Get a list of chid nodes from the node passed to the method and // find out how many children there are in the list. childNodes = node.getChildNodes(); numChildNodes = childNodes.getLength(); // Iterate through the children one at a time - it is possible for a // run to ciontain zero, one or more text nodes - and recover the text // from an text type child nodes. for (int i = 0; i < numChildNodes; i++) { // Get a node and check it's name. If this is 'w:t' then process as // text type node. childNode = childNodes.item(i); if (childNode.getNodeName().equals(Bookmark.TEXT_NODE_NAME)) { // If the node reports it's type as txet, then simply call the // getNodeValue() method to get at it's text. if (childNode.getNodeType() == Node.TEXT_NODE) { builder.append(childNode.getNodeValue()); } else { // Correct the type by parsing the node's XML markup and // creating a CTText object. Call the getStringValue() // method on that to get the text. text = CTText.Factory.parse(childNode); builder.append(text.getStringValue()); } } } return (builder.toString()); } private void handleBookmarkedCells(String bookmarkValue, int where) { List<XWPFParagraph> paraList = null; List<XWPFRun> runs = null; XWPFParagraph para = null; XWPFRun readRun = null; // Get a list if paragraphs from the table cell and remove any and all. paraList = this._tableCell.getParagraphs(); for(int i = 0; i < paraList.size(); i++) { this._tableCell.removeParagraph(i); } para = this._tableCell.addParagraph(); para.createRun().setText(bookmarkValue); } } Using them is simple. All you do is to open the Word document, creating an instance of the XWPFDocument class and then pass this to the constructor of the Bookmakrs class. From the Bookmarks class, you can get a Bookmark using it's name and then simply call the insertAtBookmark() method to set the bookmarks value. It should go something like this (exception handling omitted); File file = new File("......."); FileInputStream fis = new FileInputStream(file); XWPFDocument document = new XWPFDocument(fis); Bookmarks bookmarks = new Bookmarks(document); Bookmark bookmark = bookmarks.getBookmark("bookmakrName"); bookmark.insertAtBookmark("To be inserted", Bookmark.REPLACE); Note that there is also a method that allows you to get an Iterator<String> from the Bookmarks class. That object will allow you to iterate over the names of the bookmarks in a loop. Might be useful, might not be but it was a simple addition. -- View this message in context: http://apache-poi.1045710.n5.nabble.com/How-to-read-the-value-of-bookmarks-docx-tp5710184p5710333.html Sent from the POI - User mailing list archive at Nabble.com. --------------------------------------------------------------------- To unsubscribe, e-mail: user-unsubscr...@poi.apache.org For additional commands, e-mail: user-h...@poi.apache.org