哦,我有什麼樂趣!我有一個複雜而複雜的解決方案,我知道@JasonPlutext將提供一個我忽略的非常簡單明顯的解決方案。
這是它。此代碼生成word文檔輸出流:
outputStream = response.getOutputStream();
XHTMLImporter.setHyperlinkStyle("Hyperlink");
// Create an empty docx package
WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.createPackage();
NumberingDefinitionsPart ndp = new NumberingDefinitionsPart();
wordMLPackage.getMainDocumentPart().addTargetPart(ndp);
ndp.unmarshalDefaultNumbering();
// Convert the XHTML, and add it into the empty docx we made
List<Object> wmlObjects = getWmlObjects(wordMLPackage, xhtmlDocumentAsString);
wordMLPackage.getMainDocumentPart().getContent().addAll(wmlObjects);
SaveToZipFile saver = new SaveToZipFile(wordMLPackage);
saver.save(outputStream);
方法getWmlObjects
是我自己該模擬XHTMLImporter.convert
方法,但做每件事本身有很多反思。它基本上注入了幾個對象來覆蓋DocxRenderer
(這是導入器實例的一個字段)中的默認對象Docx4jUserAgent
和Docx4jReplacedElementFactory
。請看下圖:
private List<Object> getWmlObjects(WordprocessingMLPackage wordMLPackage, String xhtmlDocumentAsString) {
try {
DocxRenderer renderer = new DocxRenderer();
// override the user agent
FieldAccessUtils.setField(renderer, "userAgent", new ProfileImageDocx4jUserAgent());
// override the replaced element factory
Docx4jDocxOutputDevice outputDevice = (Docx4jDocxOutputDevice) FieldAccessUtils.getField(renderer,
"_outputDevice");
renderer.getSharedContext().setReplacedElementFactory(
new ProfileImageDocx4jReplacedElementFactory(outputDevice));
// build the XHTMLImporter instance as it does in XHTMLImporter.convert but with our new renderer
XHTMLImporter importer; // = new XHTMLImporter(wordMLPackage);
Constructor<XHTMLImporter> constructor = XHTMLImporter.class
.getDeclaredConstructor(WordprocessingMLPackage.class);
constructor.setAccessible(true);
importer = constructor.newInstance(wordMLPackage);
constructor.setAccessible(false);
FieldAccessUtils.setField(importer, "renderer", renderer);
InputSource is = new InputSource(new BufferedReader(new StringReader(xhtmlDocumentAsString)));
Document dom = XMLResource.load(is).getDocument();
renderer.setDocument(dom, null);
renderer.layout();
// use reflection to do: importer.traverse(renderer.getRootBox(), FieldAccessUtils.getField(importer, "imports"), null);
Method traverseMethod = importer.getClass().getDeclaredMethod("traverse", Box.class, List.class,
TableProperties.class);
traverseMethod.setAccessible(true);
traverseMethod.invoke(importer, renderer.getRootBox(), FieldAccessUtils.getField(importer, "imports"), null);
traverseMethod.setAccessible(false);
return (List<Object>) FieldAccessUtils.getField(importer, "imports");
} catch (SecurityException e) {
getLogger().error(ExceptionUtils.getStackTrace(e));
} catch (NoSuchMethodException e) {
getLogger().error(ExceptionUtils.getStackTrace(e));
} catch (IllegalArgumentException e) {
getLogger().error(ExceptionUtils.getStackTrace(e));
} catch (IllegalAccessException e) {
getLogger().error(ExceptionUtils.getStackTrace(e));
} catch (InvocationTargetException e) {
getLogger().error(ExceptionUtils.getStackTrace(e));
} catch (InstantiationException e) {
getLogger().error(ExceptionUtils.getStackTrace(e));
}
try {
// plan B
return XHTMLImporter.convert(xhtmlDocumentAsString, null, wordMLPackage);
} catch (Docx4JException e) {
getLogger().error(ExceptionUtils.getStackTrace(e));
}
return null;
}
然後我有我的兩個自定義類ProfileImageDocx4jUserAgent(它繁瑣的工作):
public class ProfileImageDocx4jUserAgent extends Docx4jUserAgent {
/**
* Replace the image where the DisplayUserPic servlet is being called.
* <p>
* From overridden method javadoc:
* <p>
* {@inheritDoc}
*/
@Override
public Docx4JFSImage getDocx4JImageResource(String uri) {
if (StringUtils.contains(uri, "DisplayUserPic")) {
InputStream input = null;
try {
input = ...;
byte[] bytes = IOUtils.toByteArray(input);
return new Docx4JFSImage(bytes);
} catch (IOException e) {
getLogger().error(ExceptionUtils.getStackTrace(e));
} catch (ServiceException e) {
getLogger().error(ExceptionUtils.getStackTrace(e));
} finally {
IOUtils.closeQuietly(input);
}
return super.getDocx4JImageResource(uri);
} else {
return super.getDocx4JImageResource(uri);
}
}
}
而且ProfileImageDocx4jReplacedElementFactory(它得到了iText的東西,在這一點上忽略圖像。 ..否則,有記錄一個錯誤,但它仍然能正常工作):
public class ProfileImageDocx4jReplacedElementFactory extends Docx4jReplacedElementFactory {
/**
* Constructor.
*
* @param outputDevice
* the output device
*/
public ProfileImageDocx4jReplacedElementFactory(Docx4jDocxOutputDevice outputDevice) {
super(outputDevice);
}
/**
* Forces any images which use the DisplayUserPic servlet to be ignored.
* <p>
* From overridden method javadoc:
* <p>
* {@inheritDoc}
*/
@Override
public ReplacedElement createReplacedElement(LayoutContext layoutContext, BlockBox blockBox,
UserAgentCallback userAgentCallback, int cssWidth, int cssHeight) {
Element element = blockBox.getElement();
if (element == null) {
return null;
}
String nodeName = element.getNodeName();
String src = element.getAttribute("src");
if ("img".equals(nodeName) && src.contains("DisplayUserPic")) {
return null;
}
// default behaviour
return super.createReplacedElement(layoutContext, blockBox, userAgentCallback, cssWidth, cssHeight);
}
}
我猜docx4j傢伙可能會建立的東西到docx4j手這種情況下,但目前(我認爲)這似乎是一個很好的工作
我已經去使用嵌入在HTML中的base 64編碼圖像,因爲我可以在轉換前做html替換,但docx4j似乎不適用於基礎64圖像 – Edd 2012-07-06 13:41:35
我已經設法做一些骯髒的反射來擴展Docx4jReplacedElementFactory並讓XHTMLImporter使用我的ReplacedElementFactory,但它不起作用。我認爲圖像不是通過ReplacedElementFactory包含的,而是在轉換的後期階段添加的 – Edd 2012-07-06 15:16:47
導入base64編碼圖像應該可以工作(請參閱第976行的XHTMLImporter) – JasonPlutext 2012-07-09 12:52:08