如何在docx4j生成的docx中从servlet渲染图像

时间:2012-07-06 11:32:20

标签: java servlets xhtml docx docx4j

这个问题与this question非常相似,但与docx4j有关,而不是飞碟。

我正在使用docx4j通过servlet将xhtml文档呈现给docx,该servlet返回生成的docx文档。 xhtml文档具有从另一个servlet请求的图像。图像servlet在返回适当的图像之前检查谁登录。下面的代码显示了如何请求图像:

<img height="140" width="140" src="http://localhost:8080/myapp/servlet/DisplayPic" />

我的问题是图像的http请求来自XHTMLImporter(我认为),而不是登录用户,因此图像servlet不知道谁登录,因此不返回所需的图像

我目前正在使用以下代码呈现xhtml文档:

XHTMLImporter.setHyperlinkStyle("Hyperlink");
WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.createPackage();

NumberingDefinitionsPart ndp = new NumberingDefinitionsPart();
wordMLPackage.getMainDocumentPart().addTargetPart(ndp);
ndp.unmarshalDefaultNumbering();

wordMLPackage.getMainDocumentPart().getContent().addAll(XHTMLImporter.convert(xhtmlDocAsString, null, wordMLPackage));

在飞碟中,我能够使用ReplacedElementFactory,但这似乎不是docx4j使用的。有没有办法在转换过程中替换元素?

1 个答案:

答案 0 :(得分:2)

哦,我有多开心!我有一个复杂,复杂和疯狂的解决方案,我知道@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方法,但是通过大量反射来完成所有事情。它基本上会注入一些对象来覆盖Docx4jUserAgent(这是Importer实例的一个字段)中的默认Docx4jReplacedElementFactoryDocxRenderer个对象。见下文:

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中构建一些东西以处理这种情况但是目前(我认为)这似乎是一个很好的解决方法