如何使用 POI 在给定段落之前插入 sdt?
How to insert a sdt before a given paragraph by using POI?
我想在现有的 word 文件中的主体之前生成目录。
我重新定义了一个自定义 XWPFDocument.createTOC 函数来生成一个风格符合我需要的目录。
但是在createTOC函数中,“this.getDocument().getBody().addNewSdt()”只能在body的最后插入一个sdt。我花了很多时间寻找一种方法来改变 sdt 元素的位置。
我发现这个方法最终可以奏效。
Node body_node = doc.getDocument().getDomNode().item(0);
NodeList nodeList = body_node.getChildNodes();
Node sdt_xxx = nodeList.item(8);
body_node.removeChild(sdt_xxx);
body_node.insertBefore(sdt_xxx ,nodeList.item(0));
该方法可以修改sdt的位置。但是这个方法对CTclass有效,所以XWPFDocument中的变量(比如List,List)在我修改CTclass后并没有改变。而且我不知道如何修改XWPFDocument中的bodyelements,这是一个不可修改的变量。修改CT变量后不知道如何重新加载XWPFDocument对象
(将修改后的对象写入新文件后重新打开文件时,XWPFDocument对象可以更新。但我不想这样做,这种方法似乎太愚蠢了。)
那么,谁知道如何使用 POI 将 sdt (TOC) 添加到给定位置?
非常感谢。
你说你已经用你自己的 createTOC
方法扩展了 XWPFDocument
。因此,您还可以按照 insertNewParagraph(org.apache.xmlbeans.XmlCursor cursor)
的方式提供 createTOC(org.apache.xmlbeans.XmlCursor cursor)
。光标确定插入目录的位置。
并且由于更新了 bodyelements 列表,扩展的 XWPFDocument
可以提供 recreateBodyElementLists
方法。然后,此方法会在调用时重新创建所有必需的主体元素列表。
说明原理的完整示例:
import java.io.FileOutputStream;
import java.io.FileInputStream;
import org.apache.poi.xwpf.usermodel.*;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTStyles;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTStyle;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTSimpleField;
import org.apache.xmlbeans.XmlCursor;
public class CreateWordTOC {
static String cTStyleTOC1 =
"<w:style xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\" w:type=\"paragraph\" w:styleId=\"TOC1\">"
+ "<w:name w:val=\"toc 1\"/>"
+ "<w:basedOn w:val=\"Normal\"/>"
+ "<w:next w:val=\"Normal\"/>"
+ "<w:autoRedefine/><w:unhideWhenUsed/>"
+ "<w:rPr><w:b/><w:bCs/><w:caps/><w:color w:val=\"C00000\"/><w:sz w:val=\"32\"/><w:szCs w:val=\"32\"/></w:rPr>"
+ "</w:style>";
static String cTStyleTOC2 =
"<w:style xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\" w:type=\"paragraph\" w:styleId=\"TOC2\">"
+ "<w:name w:val=\"toc 2\"/>"
+ "<w:basedOn w:val=\"Normal\"/>"
+ "<w:next w:val=\"Normal\"/>"
+ "<w:autoRedefine/><w:unhideWhenUsed/>"
+ "<w:rPr><w:i/><w:iCs/><w:sz w:val=\"28\"/><w:szCs w:val=\"28\"/></w:rPr>"
+ "</w:style>";
public static void main(String[] args) throws Exception {
//XWPFDocument document = new XWPFDocument(new FileInputStream("./WordDocument.docx"));
MyXWPFDocument document = new MyXWPFDocument(new FileInputStream("./WordDocument.docx"));
XWPFStyles styles = document.createStyles();
CTStyles cTStyles = CTStyles.Factory.parse(cTStyleTOC1);
CTStyle cTStyle = cTStyles.getStyleArray(0);
styles.addStyle(new XWPFStyle(cTStyle));
cTStyles = CTStyles.Factory.parse(cTStyleTOC2);
cTStyle = cTStyles.getStyleArray(0);
styles.addStyle(new XWPFStyle(cTStyle));
System.out.println(document.getBodyElements().size());
System.out.println(document.getContentControls().size());
//TOC before first parahraph
XWPFParagraph paragraph = document.getParagraphs().get(0);
XmlCursor cursor = paragraph.getCTP().newCursor();
//document.createTOC(cursor, "Heading");
document.createTOC(cursor, "berschrift"); // German style Ids are "berschrift1", "berschrift2", ... (from "Überschrift" umlaut is not used in Id)
cursor.dispose();
//TOC as last element in body
//document.createTOC(null, "Heading");
document.createTOC(null, "berschrift");
System.out.println(document.getBodyElements().size());
System.out.println(document.getContentControls().size());
if (document.getContentControls().size() > 0 )
System.out.println(document.getContentControls().get(0).getContent());
FileOutputStream out = new FileOutputStream("./WordDocumentNew.docx");
document.write(out);
out.close();
document.close();
}
}
MyXWPFDocument.java:
import java.io.*;
import java.math.BigInteger;
import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger;
import org.apache.poi.xwpf.usermodel.*;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.*;
import org.apache.xmlbeans.XmlCursor;
import org.apache.xmlbeans.XmlObject;
import java.util.List;
import java.util.ArrayList;
import java.util.Collections;
public class MyXWPFDocument extends XWPFDocument {
private static final POILogger LOG = POILogFactory.getLogger(XWPFDocument.class);
public MyXWPFDocument(InputStream is) throws IOException {
super(is);
}
public void createTOC(XmlCursor cursor, String headingStyleIdPrefix) {
CTSdtBlock block = null;
if (cursor != null && isCursorInBody(cursor)) {
String uri = CTSdtBlock.type.getName().getNamespaceURI();
String localPart = "sdt";
cursor.beginElement(localPart, uri);
cursor.toParent();
block = (CTSdtBlock) cursor.getObject();
} else {
block = this.getDocument().getBody().addNewSdt();
}
TOC toc = new TOC(block);
int bookmarkId = 0;
String tocIdPrefix = "123456";
for (XWPFParagraph par : paragraphs) {
String parStyleId = par.getStyle();
if (parStyleId != null && parStyleId.toLowerCase().startsWith(headingStyleIdPrefix.toLowerCase())) {
try {
int level = Integer.parseInt(parStyleId.substring(headingStyleIdPrefix.length()));
par.getCTP().addNewBookmarkStart();
par.getCTP().getBookmarkStartArray(0).setId(BigInteger.valueOf(bookmarkId));
par.getCTP().getBookmarkStartArray(0).setName("_Toc" + tocIdPrefix + bookmarkId);
par.getCTP().addNewBookmarkEnd().setId(BigInteger.valueOf(bookmarkId));
toc.addRow(level, par.getText(), 1, "" + tocIdPrefix + bookmarkId);
bookmarkId++;
} catch (Exception e) {
LOG.log(POILogger.ERROR, "can't create TOC item", e);
}
}
}
recreateBodyElementLists();
}
protected void recreateBodyElementLists() {
bodyElements = new ArrayList<>();
paragraphs = new ArrayList<>();
tables = new ArrayList<>();
contentControls = new ArrayList<>();
// parse the document with cursor and add
// the XmlObject to its lists
XmlCursor docCursor = this.getDocument().newCursor();
docCursor.selectPath("./*");
while (docCursor.toNextSelection()) {
XmlObject o = docCursor.getObject();
if (o instanceof CTBody) {
XmlCursor bodyCursor = o.newCursor();
bodyCursor.selectPath("./*");
while (bodyCursor.toNextSelection()) {
XmlObject bodyObj = bodyCursor.getObject();
if (bodyObj instanceof CTP) {
XWPFParagraph p = new XWPFParagraph((CTP) bodyObj, this);
bodyElements.add(p);
paragraphs.add(p);
} else if (bodyObj instanceof CTTbl) {
XWPFTable t = new XWPFTable((CTTbl) bodyObj, this);
bodyElements.add(t);
tables.add(t);
} else if (bodyObj instanceof CTSdtBlock) {
XWPFSDT c = new XWPFSDT((CTSdtBlock) bodyObj, this);
bodyElements.add(c);
contentControls.add(c);
}
}
bodyCursor.dispose();
}
}
docCursor.dispose();
}
public List<XWPFSDT> getContentControls() {
return Collections.unmodifiableList(contentControls);
}
private boolean isCursorInBody(XmlCursor cursor) {
XmlCursor verify = cursor.newCursor();
verify.toParent();
boolean result = (verify.getObject() == getDocument().getBody());
verify.dispose();
return result;
}
}
在我看来org.apache.poi.xwpf.usermodel.TOC
也是不完整的,需要扩展。不过这又是另外一回事了。
我想在现有的 word 文件中的主体之前生成目录。 我重新定义了一个自定义 XWPFDocument.createTOC 函数来生成一个风格符合我需要的目录。 但是在createTOC函数中,“this.getDocument().getBody().addNewSdt()”只能在body的最后插入一个sdt。我花了很多时间寻找一种方法来改变 sdt 元素的位置。 我发现这个方法最终可以奏效。
Node body_node = doc.getDocument().getDomNode().item(0);
NodeList nodeList = body_node.getChildNodes();
Node sdt_xxx = nodeList.item(8);
body_node.removeChild(sdt_xxx);
body_node.insertBefore(sdt_xxx ,nodeList.item(0));
该方法可以修改sdt的位置。但是这个方法对CTclass有效,所以XWPFDocument中的变量(比如List
(将修改后的对象写入新文件后重新打开文件时,XWPFDocument对象可以更新。但我不想这样做,这种方法似乎太愚蠢了。)
那么,谁知道如何使用 POI 将 sdt (TOC) 添加到给定位置? 非常感谢。
你说你已经用你自己的 createTOC
方法扩展了 XWPFDocument
。因此,您还可以按照 insertNewParagraph(org.apache.xmlbeans.XmlCursor cursor)
的方式提供 createTOC(org.apache.xmlbeans.XmlCursor cursor)
。光标确定插入目录的位置。
并且由于更新了 bodyelements 列表,扩展的 XWPFDocument
可以提供 recreateBodyElementLists
方法。然后,此方法会在调用时重新创建所有必需的主体元素列表。
说明原理的完整示例:
import java.io.FileOutputStream;
import java.io.FileInputStream;
import org.apache.poi.xwpf.usermodel.*;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTStyles;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTStyle;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTSimpleField;
import org.apache.xmlbeans.XmlCursor;
public class CreateWordTOC {
static String cTStyleTOC1 =
"<w:style xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\" w:type=\"paragraph\" w:styleId=\"TOC1\">"
+ "<w:name w:val=\"toc 1\"/>"
+ "<w:basedOn w:val=\"Normal\"/>"
+ "<w:next w:val=\"Normal\"/>"
+ "<w:autoRedefine/><w:unhideWhenUsed/>"
+ "<w:rPr><w:b/><w:bCs/><w:caps/><w:color w:val=\"C00000\"/><w:sz w:val=\"32\"/><w:szCs w:val=\"32\"/></w:rPr>"
+ "</w:style>";
static String cTStyleTOC2 =
"<w:style xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\" w:type=\"paragraph\" w:styleId=\"TOC2\">"
+ "<w:name w:val=\"toc 2\"/>"
+ "<w:basedOn w:val=\"Normal\"/>"
+ "<w:next w:val=\"Normal\"/>"
+ "<w:autoRedefine/><w:unhideWhenUsed/>"
+ "<w:rPr><w:i/><w:iCs/><w:sz w:val=\"28\"/><w:szCs w:val=\"28\"/></w:rPr>"
+ "</w:style>";
public static void main(String[] args) throws Exception {
//XWPFDocument document = new XWPFDocument(new FileInputStream("./WordDocument.docx"));
MyXWPFDocument document = new MyXWPFDocument(new FileInputStream("./WordDocument.docx"));
XWPFStyles styles = document.createStyles();
CTStyles cTStyles = CTStyles.Factory.parse(cTStyleTOC1);
CTStyle cTStyle = cTStyles.getStyleArray(0);
styles.addStyle(new XWPFStyle(cTStyle));
cTStyles = CTStyles.Factory.parse(cTStyleTOC2);
cTStyle = cTStyles.getStyleArray(0);
styles.addStyle(new XWPFStyle(cTStyle));
System.out.println(document.getBodyElements().size());
System.out.println(document.getContentControls().size());
//TOC before first parahraph
XWPFParagraph paragraph = document.getParagraphs().get(0);
XmlCursor cursor = paragraph.getCTP().newCursor();
//document.createTOC(cursor, "Heading");
document.createTOC(cursor, "berschrift"); // German style Ids are "berschrift1", "berschrift2", ... (from "Überschrift" umlaut is not used in Id)
cursor.dispose();
//TOC as last element in body
//document.createTOC(null, "Heading");
document.createTOC(null, "berschrift");
System.out.println(document.getBodyElements().size());
System.out.println(document.getContentControls().size());
if (document.getContentControls().size() > 0 )
System.out.println(document.getContentControls().get(0).getContent());
FileOutputStream out = new FileOutputStream("./WordDocumentNew.docx");
document.write(out);
out.close();
document.close();
}
}
MyXWPFDocument.java:
import java.io.*;
import java.math.BigInteger;
import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger;
import org.apache.poi.xwpf.usermodel.*;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.*;
import org.apache.xmlbeans.XmlCursor;
import org.apache.xmlbeans.XmlObject;
import java.util.List;
import java.util.ArrayList;
import java.util.Collections;
public class MyXWPFDocument extends XWPFDocument {
private static final POILogger LOG = POILogFactory.getLogger(XWPFDocument.class);
public MyXWPFDocument(InputStream is) throws IOException {
super(is);
}
public void createTOC(XmlCursor cursor, String headingStyleIdPrefix) {
CTSdtBlock block = null;
if (cursor != null && isCursorInBody(cursor)) {
String uri = CTSdtBlock.type.getName().getNamespaceURI();
String localPart = "sdt";
cursor.beginElement(localPart, uri);
cursor.toParent();
block = (CTSdtBlock) cursor.getObject();
} else {
block = this.getDocument().getBody().addNewSdt();
}
TOC toc = new TOC(block);
int bookmarkId = 0;
String tocIdPrefix = "123456";
for (XWPFParagraph par : paragraphs) {
String parStyleId = par.getStyle();
if (parStyleId != null && parStyleId.toLowerCase().startsWith(headingStyleIdPrefix.toLowerCase())) {
try {
int level = Integer.parseInt(parStyleId.substring(headingStyleIdPrefix.length()));
par.getCTP().addNewBookmarkStart();
par.getCTP().getBookmarkStartArray(0).setId(BigInteger.valueOf(bookmarkId));
par.getCTP().getBookmarkStartArray(0).setName("_Toc" + tocIdPrefix + bookmarkId);
par.getCTP().addNewBookmarkEnd().setId(BigInteger.valueOf(bookmarkId));
toc.addRow(level, par.getText(), 1, "" + tocIdPrefix + bookmarkId);
bookmarkId++;
} catch (Exception e) {
LOG.log(POILogger.ERROR, "can't create TOC item", e);
}
}
}
recreateBodyElementLists();
}
protected void recreateBodyElementLists() {
bodyElements = new ArrayList<>();
paragraphs = new ArrayList<>();
tables = new ArrayList<>();
contentControls = new ArrayList<>();
// parse the document with cursor and add
// the XmlObject to its lists
XmlCursor docCursor = this.getDocument().newCursor();
docCursor.selectPath("./*");
while (docCursor.toNextSelection()) {
XmlObject o = docCursor.getObject();
if (o instanceof CTBody) {
XmlCursor bodyCursor = o.newCursor();
bodyCursor.selectPath("./*");
while (bodyCursor.toNextSelection()) {
XmlObject bodyObj = bodyCursor.getObject();
if (bodyObj instanceof CTP) {
XWPFParagraph p = new XWPFParagraph((CTP) bodyObj, this);
bodyElements.add(p);
paragraphs.add(p);
} else if (bodyObj instanceof CTTbl) {
XWPFTable t = new XWPFTable((CTTbl) bodyObj, this);
bodyElements.add(t);
tables.add(t);
} else if (bodyObj instanceof CTSdtBlock) {
XWPFSDT c = new XWPFSDT((CTSdtBlock) bodyObj, this);
bodyElements.add(c);
contentControls.add(c);
}
}
bodyCursor.dispose();
}
}
docCursor.dispose();
}
public List<XWPFSDT> getContentControls() {
return Collections.unmodifiableList(contentControls);
}
private boolean isCursorInBody(XmlCursor cursor) {
XmlCursor verify = cursor.newCursor();
verify.toParent();
boolean result = (verify.getObject() == getDocument().getBody());
verify.dispose();
return result;
}
}
在我看来org.apache.poi.xwpf.usermodel.TOC
也是不完整的,需要扩展。不过这又是另外一回事了。