【问题标题】:How to render image from servlet in docx4j generated docx如何在 docx4j 生成的 docx 中从 servlet 渲染图像
【发布时间】:2012-07-06 11:32:20
【问题描述】:

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

我正在使用 docx4j 通过返回生成的 docx 文档的 servlet 将 xhtml 文档呈现给 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 使用的东西。有没有办法在转换过程中替换元素?

【问题讨论】:

  • 我尝试过使用嵌入在 html 中的 base 64 编码图像,因为我可以在转换之前进行 html 替换,但 docx4j 似乎不适用于 base 64 图像
  • 我已经设法做一些肮脏的反射来扩展 Docx4jReplacedElementFactory 并让 XHTMLImporter 使用我的 ReplacedElementFactory 但它不起作用。我认为图像不是通过 ReplacedElementFactory 包含的,而是在转换的后期添加的
  • base64 编码图像的导入应该可以工作(参见第 976 行的 XHTMLImporter)
  • 那里似乎没有任何与 base 64 相关的内容...我使用的是 2.8.0 版
  • 看过github 并且可以在那里看到base64 的东西,但版本是2.8.1-SNAPSHOT,除非必要,我宁愿推迟使用快照版本。请添加您的建议作为答案,因为它似乎是更理想的解决方案

标签: java servlets xhtml docx docx4j


【解决方案1】:

哦,我玩得很开心!我有一个复杂、复杂和疯狂的解决方案,我知道@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(这是Importer 实例的一个字段)中的默认Docx4jUserAgentDocx4jReplacedElementFactory 对象。见下文:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-05-06
    • 2023-02-11
    • 2012-11-08
    • 1970-01-01
    • 2017-11-15
    • 2013-04-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多