Java Docx4j - 如何用值替换占位符

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/20484722/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 02:14:40  来源:igfitidea点击:

Docx4j - How to replace placeholder with value

javadocxdocx4j

提问by Stephane Grenier

I've been trying to work through the examples FieldMailMergeand VariableReplacebut can't seem to get a local test case running. I'm basically trying to start with one docx template document and have it create x docx documents from that one template with the variables replaced.

我一直在尝试处理FieldMailMergeVariableReplace示例, 但似乎无法运行本地测试用例。我基本上是尝试从一个 docx 模板文档开始,并让它从该模板创建 x 个 docx 文档,并替换变量。

In the code below docx4jReplaceSimpleTest()tries to replace a single variable but fails to do so. The ${} values in the template files are removed as part of the processing therefore I believe it's finding them but not replacing them for some reason. I understand it could be due to formatting as explained in the comments of the sample code but for troubleshooting just to get something working I'm trying it anyways.

在下面的代码中,docx4jReplaceSimpleTest()尝试替换单个变量但没有这样做。模板文件中的 ${} 值作为处理的一部分被删除,因此我相信它正在查找它们但由于某种原因没有替换它们。我知道这可能是由于示例代码的注释中所解释的格式,但为了进行故障排除只是为了让某些工作正常工作,我无论如何都在尝试。

In the code below docx4jReplaceTwoPeopleTest(), the one I want to get working, I'm trying to do it in what I believe is the proper way, but that's not finding or replacing anything. It's not even removing the ${} from the docx file.

在下面的代码中docx4jReplaceTwoPeopleTest(),我想要开始工作,我正在尝试以我认为正确的方式进行操作,但这并没有找到或替换任何内容。它甚至没有从 docx 文件中删除 ${}。

public static void main(String[] args) throws Exception
{
    docx4jReplaceTwoPeopleTest();
    docx4jReplaceSimpleTest();
}

private static void docx4jReplaceTwoPeopleTest() throws Exception
{
      String docxFile = "C:/temp/template.docx";

      WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.load(new java.io.File(docxFile));

      List<Map<DataFieldName, String>> data = new ArrayList<Map<DataFieldName, String>>();

      Map<DataFieldName, String> map1 = new HashMap<DataFieldName, String>();
      map1.put(new DataFieldName("Person.Firstname"), "myFirstname");
      map1.put(new DataFieldName("Person.Lastname"), "myLastname");
      data.add(map1);

      Map<DataFieldName, String> map2 = new HashMap<DataFieldName, String>();
      map2.put(new DataFieldName("Person.Firstname"), "myFriendsFirstname");
      map2.put(new DataFieldName("Person.Lastname"), "myFriendsLastname");
      data.add(map2);

      org.docx4j.model.fields.merge.MailMerger.setMERGEFIELDInOutput(OutputField.KEEP_MERGEFIELD);

      int x=0;
      for(Map<DataFieldName, String> docMapping: data) 
      {
        org.docx4j.model.fields.merge.MailMerger.performMerge(wordMLPackage, docMapping, true);
        wordMLPackage.save(new java.io.File("C:/temp/OUT__MAIL_MERGE_" + x++ + ".docx") );
      }
}

private static void docx4jReplaceSimpleTest() throws Exception
{
      String docxFile = "C:/temp/template.docx";

      WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.load(new java.io.File(docxFile));

      HashMap<String, String> mappings = new HashMap<String, String>();
      mappings.put("Person.Firstname", "myFirstname");
      mappings.put("Person.Lastname", "myLastname");

      MainDocumentPart documentPart = wordMLPackage.getMainDocumentPart();
      documentPart.variableReplace(mappings);

    wordMLPackage.save(new java.io.File("C:/temp/OUT_SIMPLE.docx") );
}

The docx file consists of the following text (no formatting is done):

docx 文件由以下文本组成(未进行格式化):

This is a letter to someone
Hi ${Person.Firstname} ${Person.Lastname},
How are you?
Thank you again. I wish to see you soon ${Person.Firstname}
Regards,
Someone

Notice that I'm also trying to replace Person.Firstname at least twice as well. As the lastname is not even replaced I don't think this has anything to do with it but I'm adding it just in case.

请注意,我还尝试至少两次替换 Person.Firstname。由于姓氏甚至没有被替换,我认为这与它没有任何关系,但我添加它以防万一。

回答by Stephane Grenier

The issue is that I was trying to create the placeholders as just plain text within the docx file. What I should've been doing instead is using the MergeField functionality within Word which I didn't fully understand and appreciate, hence the confusion. Basically I didn't know that this is what was being meant within the documentation because I'd never used it, I just assumed it was still some kind of xml text replacement.

问题是我试图在 docx 文件中将占位符创建为纯文本。我应该做的是在 Word 中使用 MergeField 功能,我没有完全理解和欣赏,因此造成了混乱。基本上我不知道这是文档中的意思,因为我从未使用过它,我只是假设它仍然是某种 xml 文本替换。

That being said it's still fairly difficult to find a good explanation of this Word feature. After looking at a few dozen explanations I still couldn't find a nice clean explanation of this Word feature. The best explanation I was able find can be found here. Basically you want to do Step 3.

话虽如此,要找到有关此 Word 功能的良好解释仍然相当困难。在看了几十个解释之后,我仍然找不到对这个 Word 功能的一个很好的清晰解释。我能找到的最好的解释可以在这里找到。基本上你想做第3步。

That being said, once I created MergeFields in Word and ran the code, it worked perfectly. The method to use is docx4jReplaceTwoPeopleTest. The problem wasn't in the code but in my understanding of how it worked within Word.

话虽如此,一旦我在 Word 中创建了 MergeFields 并运行了代码,它就完美地工作了。使用方法是docx4jReplaceTwoPeopleTest问题不在于代码,而在于我对它在 Word 中的工作方式的理解。

回答by demotics2002

I had the same issue and of course I could not force user to do some extra stuff when composing their word document so I decided to just write an algo to scan the whole document for expressions appending run after run, inserting replacement value and remove expressions in the second run. In case other people may need it below is what I did. I got the class from somewhere so it may be familiar. I just added the method searchAndReplace()

我有同样的问题,当然我不能强迫用户在撰写他们的 word 文档时做一些额外的事情,所以我决定只写一个算法来扫描整个文档以查找在运行后附加的表达式,插入替换值并删除表达式第二次运行。如果其他人可能需要它,我就是这样做的。我从某个地方得到了这门课,所以它可能很熟悉。我刚刚添加了方法 searchAndReplace()

package com.my.docx4j;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;

import org.docx4j.openpackaging.exceptions.Docx4JException;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.docx4j.wml.ContentAccessor;
import org.docx4j.wml.Text;

public class Docx4j {

    public static void main(String[] args) throws Docx4JException, IOException, JAXBException {
        String filePath = "C:\Users\markamm\Documents\tmp\";
        String file = "Hello.docx";

        Docx4j docx4j = new Docx4j();
        WordprocessingMLPackage template = docx4j.getTemplate(filePath+file);

//      MainDocumentPart documentPart = template.getMainDocumentPart();

        List<Object> texts = getAllElementFromObject(
                template.getMainDocumentPart(), Text.class);
        searchAndReplace(texts, new HashMap<String, String>(){
            {
                this.put("${abcd_efg.soanother_hello_broken_shit}", "Company Name here...");
                this.put("${I_dont_know}", "Hmmm lemme see");
                this.put("${${damn.right_lol}", "Gotcha!!!");
                this.put("${one_here_and}", "Firstname");
                this.put("${one}", "ChildA");
                this.put("${two}", "ChildB");
                this.put("${three}", "ChildC");
            }
            @Override
            public String get(Object key) {
                // TODO Auto-generated method stub
                return super.get(key);
            }
        });

        docx4j.writeDocxToStream(template, filePath+"Hello2.docx");
    }

    public static void searchAndReplace(List<Object> texts, Map<String, String> values){

        // -- scan all expressions  
        // Will later contain all the expressions used though not used at the moment
        List<String> els = new ArrayList<String>(); 

        StringBuilder sb = new StringBuilder();
        int PASS = 0;
        int PREPARE = 1;
        int READ = 2;
        int mode = PASS;

        // to nullify
        List<int[]> toNullify = new ArrayList<int[]>();
        int[] currentNullifyProps = new int[4];

        // Do scan of els and immediately insert value
        for(int i = 0; i<texts.size(); i++){
            Object text = texts.get(i);
            Text textElement = (Text) text;
            String newVal = "";
            String v = textElement.getValue();
//          System.out.println("text: "+v);
            StringBuilder textSofar = new StringBuilder();
            int extra = 0;
            char[] vchars = v.toCharArray();
            for(int col = 0; col<vchars.length; col++){
                char c = vchars[col];
                textSofar.append(c);
                switch(c){
                case '$': {
                    mode=PREPARE;
                    sb.append(c);
//                  extra = 0;
                } break;
                case '{': {
                    if(mode==PREPARE){
                        sb.append(c);
                        mode=READ;
                        currentNullifyProps[0]=i;
                        currentNullifyProps[1]=col+extra-1;
                        System.out.println("extra-- "+extra);
                    } else {
                        if(mode==READ){
                            // consecutive opening curl found. just read it
                            // but supposedly throw error
                            sb = new StringBuilder();
                            mode=PASS;
                        }
                    }
                } break;
                case '}': {
                    if(mode==READ){
                        mode=PASS;
                        sb.append(c);
                        els.add(sb.toString());
                        newVal +=textSofar.toString()
                                +(null==values.get(sb.toString())?sb.toString():values.get(sb.toString()));
                        textSofar = new StringBuilder();
                        currentNullifyProps[2]=i;
                        currentNullifyProps[3]=col+extra;
                        toNullify.add(currentNullifyProps);
                        currentNullifyProps = new int[4];
                        extra += sb.toString().length();
                        sb = new StringBuilder();
                    } else if(mode==PREPARE){
                        mode = PASS;
                        sb = new StringBuilder();
                    }
                }
                default: {
                    if(mode==READ) sb.append(c);
                    else if(mode==PREPARE){
                        mode=PASS;
                        sb = new StringBuilder();
                    }
                }
                }
            }
            newVal +=textSofar.toString();
            textElement.setValue(newVal);
        }

        // remove original expressions
        if(toNullify.size()>0)
        for(int i = 0; i<texts.size(); i++){
            if(toNullify.size()==0) break;
            currentNullifyProps = toNullify.get(0);
            Object text = texts.get(i);
            Text textElement = (Text) text;
            String v = textElement.getValue();
            StringBuilder nvalSB = new StringBuilder();
            char[] textChars = v.toCharArray();
            for(int j = 0; j<textChars.length; j++){
                char c = textChars[j];
                if(null==currentNullifyProps) {
                    nvalSB.append(c);
                    continue;
                }
                // I know 100000 is too much!!! And so what???
                int floor = currentNullifyProps[0]*100000+currentNullifyProps[1];
                int ceil = currentNullifyProps[2]*100000+currentNullifyProps[3];
                int head = i*100000+j;
                if(!(head>=floor && head<=ceil)){
                    nvalSB.append(c);
                } 

                if(j>currentNullifyProps[3] && i>=currentNullifyProps[2]){
                    toNullify.remove(0);
                    if(toNullify.size()==0) {
                        currentNullifyProps = null;
                        continue;
                    }
                    currentNullifyProps = toNullify.get(0);
                }
            }
            textElement.setValue(nvalSB.toString());
        }
    }

    private WordprocessingMLPackage getTemplate(String name)
            throws Docx4JException, FileNotFoundException {
        WordprocessingMLPackage template = WordprocessingMLPackage
                .load(new FileInputStream(new File(name)));
        return template;
    }

    private static List<Object> getAllElementFromObject(Object obj,
            Class<?> toSearch) {
        List<Object> result = new ArrayList<Object>();
        if (obj instanceof JAXBElement)
            obj = ((JAXBElement<?>) obj).getValue();

        if (obj.getClass().equals(toSearch))
            result.add(obj);
        else if (obj instanceof ContentAccessor) {
            List<?> children = ((ContentAccessor) obj).getContent();
            for (Object child : children) {
                result.addAll(getAllElementFromObject(child, toSearch));
            }

        }
        return result;
    }

    private void replacePlaceholder(WordprocessingMLPackage template,
            String name, String placeholder) {
        List<Object> texts = getAllElementFromObject(
                template.getMainDocumentPart(), Text.class);

        for (Object text : texts) {
            Text textElement = (Text) text;
            if (textElement.getValue().equals(placeholder)) {
                textElement.setValue(name);
            }
        }
    }

    private void writeDocxToStream(WordprocessingMLPackage template,
            String target) throws IOException, Docx4JException {
        File f = new File(target);
        template.save(f);
    }
}