为什么我不能进行多页邮件合并?
Why I can't do a multipage mail merge?
我目前正在尝试进行多页邮件合并,问题是目前我只是用第二张地图覆盖了内容。
这是重要的代码片段:
public static void main(final String[] args) throws Docx4JException, JAXBException
{
final WordprocessingMLPackage word = Docx4J.load(new File(filePath));
final MainDocumentPart document = word.getMainDocumentPart();
generatePagesFromTemplate(document);
final List<Map<DataFieldName, String>> data = prepareForMailMerge();
for (int i = 0; i < data.size(); i++)
{
MailMerger.performMerge(word, data.get(i), false);
}
// This is a workaround, otherwise the document would have an error
setRandomIdsForDocPr(document);
word.save(new File(outputPath));
createOutputXml(document); // just writes document.getXML() in a file
openFile(outputPath);
}
private static void generatePagesFromTemplate(final MainDocumentPart document, final int nrOfSheets)
{
final List<Object> pageContent = document.getContent();
// This is needed if you don't want a endless loop
final int nrOfElements = pageContent.size();
// Make a copy of the first sheet, to the nr of pages that exist
for (int sheetNr = 1; sheetNr < nrOfSheets; sheetNr++)
{
addPageBreak(document);
for (int i = 0; i < nrOfElements; i++)
{
final Object tmp = pageContent.get(i);
document.addObject(tmp);
System.out.println("Added object: " + tmp.toString());
}
}
}
private static void setRandomIdsForDocPr(final MainDocumentPart document)
throws JAXBException, XPathBinderAssociationIsPartialException
{
final String xpath = "//wp:docPr";
final List<Object> docPr = document.getJAXBNodesViaXPath(xpath, false);
for (int i = 0; i < docPr.size(); i++)
{
final CTNonVisualDrawingProps props = (CTNonVisualDrawingProps) docPr.get(i);
props.setId(setRandomValue());
}
}
private static List<Map<DataFieldName, String>> prepareForMailMerge()
{
final List<Map<DataFieldName, String>> data = new ArrayList<Map<DataFieldName, String>>();
// Instance 1
Map<DataFieldName, String> map = new HashMap<DataFieldName, String>();
map.put(new DataFieldName("Field1"), "Daffy duck");
map.put(new DataFieldName("Field2"), "Plutext");
data.add(map);
// Instance 2
map = new HashMap<DataFieldName, String>();
map.put(new DataFieldName("Field1"), "duck Daffy");
map.put(new DataFieldName("Field2"), "ThisPlutext");
data.add(map);
// Choose how to treat the MERGEFIELD in the output
MailMerger.setMERGEFIELDInOutput(OutputField.KEEP_MERGEFIELD);
return data;
}
在这里你可以找到my document as XML(docPr在现实中有不同的id,只是用这个文件做其他问题)
所以我认为有两种方法可以解决这个问题:
- 更改每个合并字段的合并字段名称
独一无二
- 仅在一页上合并,因此我不必重命名每个字段
我认为必须有一种方法可以为每个页面处理这个问题,还是我错了?
我也试过变量替换,但这对文本字段不起作用,我现在尝试进入内容控件,也许这会解决我的问题。
将其留给 MailMerger 克隆内容。尝试类似的东西:
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import javax.xml.bind.JAXBException;
import org.docx4j.XmlUtils;
import org.docx4j.dml.CTNonVisualDrawingProps;
import org.docx4j.jaxb.XPathBinderAssociationIsPartialException;
import org.docx4j.model.fields.merge.DataFieldName;
import org.docx4j.model.fields.merge.MailMerger.OutputField;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.docx4j.openpackaging.parts.WordprocessingML.MainDocumentPart;
public class FieldsMailMerge {
public static void main(String[] args) throws Exception {
WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.load(
new java.io.File(
System.getProperty("user.dir") + "/_tmp_textbox.docx"));
List<Map<DataFieldName, String>> data = prepareForMailMerge();
// System.out.println(XmlUtils.marshaltoString(wordMLPackage.getMainDocumentPart().getJaxbElement(), true, true));
WordprocessingMLPackage output = org.docx4j.model.fields.merge.MailMerger.getConsolidatedResultCrude(wordMLPackage, data, true);
// System.out.println(XmlUtils.marshaltoString(output.getMainDocumentPart().getJaxbElement(), true, true));
output.save(new java.io.File(
System.getProperty("user.dir") + "/OUT_FieldsMailMerge.docx") );
}
private static List<Map<DataFieldName, String>> prepareForMailMerge()
{
final List<Map<DataFieldName, String>> data = new ArrayList<Map<DataFieldName, String>>();
// Instance 1
Map<DataFieldName, String> map = new HashMap<DataFieldName, String>();
map.put(new DataFieldName("Field1"), "Daffy duck 1");
map.put(new DataFieldName("Field2"), "Plutext 1");
data.add(map);
// Instance 2
map = new HashMap<DataFieldName, String>();
map.put(new DataFieldName("Field1"), "duck Daffy 2");
map.put(new DataFieldName("Field2"), "ThisPlutext 2");
data.add(map);
// Choose how to treat the MERGEFIELD in the output
org.docx4j.model.fields.merge.MailMerger.setMERGEFIELDInOutput(OutputField.KEEP_MERGEFIELD);
return data;
}
private static void setRandomIdsForDocPr(final MainDocumentPart document)
throws JAXBException, XPathBinderAssociationIsPartialException
{
final String xpath = "//wp:docPr";
final List<Object> docPr = document.getJAXBNodesViaXPath(xpath, false);
for (int i = 0; i < docPr.size(); i++)
{
final CTNonVisualDrawingProps props = (CTNonVisualDrawingProps) docPr.get(i);
props.setId(setRandomValue());
}
}
private static long setRandomValue()
{
final Random random = new Random();
final int min = 1000;
final int max = 9999;
return random.nextInt((max - min) + 1) + min;
}
}
好的,我刚刚为文本对象实现了自己的简单邮件合并。
我希望它可能对将来的某个人有用:)
- 简单版:
private static void textboxMailMerge(final MainDocumentPart document)
throws XPathBinderAssociationIsPartialException, JAXBException
{
final String xpath = "//w:t";
final List<Object> text = document.getJAXBNodesViaXPath(xpath, false);
for (int i = 0; i < text.size(); i++)
{
@SuppressWarnings("unchecked")
final JAXBElement<Text> tmp = (JAXBElement<Text>) text.get(i);
final Text txt = new Text();
txt.setValue("Test" + i);
tmp.setValue(txt);
}
}
- 更复杂的版本,我认为可以简化它
更多,但对我来说它有效:)
private static void textboxMailMerge(final MainDocumentPart document, final int nrOfSheets)
{
final String xpath = "//wps:txbx/w:txbxContent/w:p/w:r/w:t";
try
{
final List<Object> textList = document.getJAXBNodesViaXPath(xpath, false);
final int dataSize = data.size();
final int listSize = textList.size();
// check if the size is valid
if (dataSize == listSize / nrOfSheets)
{
// iterate over every text element
for (int i = 0; i < listSize; i++)
{
// iterate over every map
for (final Map<DataFieldName, String> map : data)
{
// iterate over every value from the map
for (final String value : map.values())
{
@SuppressWarnings("unchecked")
final JAXBElement<Text> element = (JAXBElement<Text>) textList.get(i);
final Text txt = new Text();
txt.setValue(value);
element.setValue(txt);
}
}
}
}
else
{
System.err.println("The size of the text elements isn't equal with the size of the data map!");
}
}
catch (XPathBinderAssociationIsPartialException | JAXBException e)
{
e.printStackTrace();
}
}
这是数据变量的样子:List<Map<DataFieldName, String>> data
所以复杂版本只是遍历所有数据并用给定文本替换合并字段的文本对象,合并字段的名称将保持不变。
我明天会测试复杂的版本,但我认为它应该可以:)
唯一让我有点恼火的是未经检查的投射警告,也许有人知道如何解决这个问题。我看了一些问题,espacilly this one is quite popular 但我不明白我在这里做错了什么。
我目前正在尝试进行多页邮件合并,问题是目前我只是用第二张地图覆盖了内容。
这是重要的代码片段:
public static void main(final String[] args) throws Docx4JException, JAXBException
{
final WordprocessingMLPackage word = Docx4J.load(new File(filePath));
final MainDocumentPart document = word.getMainDocumentPart();
generatePagesFromTemplate(document);
final List<Map<DataFieldName, String>> data = prepareForMailMerge();
for (int i = 0; i < data.size(); i++)
{
MailMerger.performMerge(word, data.get(i), false);
}
// This is a workaround, otherwise the document would have an error
setRandomIdsForDocPr(document);
word.save(new File(outputPath));
createOutputXml(document); // just writes document.getXML() in a file
openFile(outputPath);
}
private static void generatePagesFromTemplate(final MainDocumentPart document, final int nrOfSheets)
{
final List<Object> pageContent = document.getContent();
// This is needed if you don't want a endless loop
final int nrOfElements = pageContent.size();
// Make a copy of the first sheet, to the nr of pages that exist
for (int sheetNr = 1; sheetNr < nrOfSheets; sheetNr++)
{
addPageBreak(document);
for (int i = 0; i < nrOfElements; i++)
{
final Object tmp = pageContent.get(i);
document.addObject(tmp);
System.out.println("Added object: " + tmp.toString());
}
}
}
private static void setRandomIdsForDocPr(final MainDocumentPart document)
throws JAXBException, XPathBinderAssociationIsPartialException
{
final String xpath = "//wp:docPr";
final List<Object> docPr = document.getJAXBNodesViaXPath(xpath, false);
for (int i = 0; i < docPr.size(); i++)
{
final CTNonVisualDrawingProps props = (CTNonVisualDrawingProps) docPr.get(i);
props.setId(setRandomValue());
}
}
private static List<Map<DataFieldName, String>> prepareForMailMerge()
{
final List<Map<DataFieldName, String>> data = new ArrayList<Map<DataFieldName, String>>();
// Instance 1
Map<DataFieldName, String> map = new HashMap<DataFieldName, String>();
map.put(new DataFieldName("Field1"), "Daffy duck");
map.put(new DataFieldName("Field2"), "Plutext");
data.add(map);
// Instance 2
map = new HashMap<DataFieldName, String>();
map.put(new DataFieldName("Field1"), "duck Daffy");
map.put(new DataFieldName("Field2"), "ThisPlutext");
data.add(map);
// Choose how to treat the MERGEFIELD in the output
MailMerger.setMERGEFIELDInOutput(OutputField.KEEP_MERGEFIELD);
return data;
}
在这里你可以找到my document as XML(docPr在现实中有不同的id,只是用这个文件做其他问题)
所以我认为有两种方法可以解决这个问题:
- 更改每个合并字段的合并字段名称 独一无二
- 仅在一页上合并,因此我不必重命名每个字段
我认为必须有一种方法可以为每个页面处理这个问题,还是我错了?
我也试过变量替换,但这对文本字段不起作用,我现在尝试进入内容控件,也许这会解决我的问题。
将其留给 MailMerger 克隆内容。尝试类似的东西:
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import javax.xml.bind.JAXBException;
import org.docx4j.XmlUtils;
import org.docx4j.dml.CTNonVisualDrawingProps;
import org.docx4j.jaxb.XPathBinderAssociationIsPartialException;
import org.docx4j.model.fields.merge.DataFieldName;
import org.docx4j.model.fields.merge.MailMerger.OutputField;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.docx4j.openpackaging.parts.WordprocessingML.MainDocumentPart;
public class FieldsMailMerge {
public static void main(String[] args) throws Exception {
WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.load(
new java.io.File(
System.getProperty("user.dir") + "/_tmp_textbox.docx"));
List<Map<DataFieldName, String>> data = prepareForMailMerge();
// System.out.println(XmlUtils.marshaltoString(wordMLPackage.getMainDocumentPart().getJaxbElement(), true, true));
WordprocessingMLPackage output = org.docx4j.model.fields.merge.MailMerger.getConsolidatedResultCrude(wordMLPackage, data, true);
// System.out.println(XmlUtils.marshaltoString(output.getMainDocumentPart().getJaxbElement(), true, true));
output.save(new java.io.File(
System.getProperty("user.dir") + "/OUT_FieldsMailMerge.docx") );
}
private static List<Map<DataFieldName, String>> prepareForMailMerge()
{
final List<Map<DataFieldName, String>> data = new ArrayList<Map<DataFieldName, String>>();
// Instance 1
Map<DataFieldName, String> map = new HashMap<DataFieldName, String>();
map.put(new DataFieldName("Field1"), "Daffy duck 1");
map.put(new DataFieldName("Field2"), "Plutext 1");
data.add(map);
// Instance 2
map = new HashMap<DataFieldName, String>();
map.put(new DataFieldName("Field1"), "duck Daffy 2");
map.put(new DataFieldName("Field2"), "ThisPlutext 2");
data.add(map);
// Choose how to treat the MERGEFIELD in the output
org.docx4j.model.fields.merge.MailMerger.setMERGEFIELDInOutput(OutputField.KEEP_MERGEFIELD);
return data;
}
private static void setRandomIdsForDocPr(final MainDocumentPart document)
throws JAXBException, XPathBinderAssociationIsPartialException
{
final String xpath = "//wp:docPr";
final List<Object> docPr = document.getJAXBNodesViaXPath(xpath, false);
for (int i = 0; i < docPr.size(); i++)
{
final CTNonVisualDrawingProps props = (CTNonVisualDrawingProps) docPr.get(i);
props.setId(setRandomValue());
}
}
private static long setRandomValue()
{
final Random random = new Random();
final int min = 1000;
final int max = 9999;
return random.nextInt((max - min) + 1) + min;
}
}
好的,我刚刚为文本对象实现了自己的简单邮件合并。 我希望它可能对将来的某个人有用:)
- 简单版:
private static void textboxMailMerge(final MainDocumentPart document)
throws XPathBinderAssociationIsPartialException, JAXBException
{
final String xpath = "//w:t";
final List<Object> text = document.getJAXBNodesViaXPath(xpath, false);
for (int i = 0; i < text.size(); i++)
{
@SuppressWarnings("unchecked")
final JAXBElement<Text> tmp = (JAXBElement<Text>) text.get(i);
final Text txt = new Text();
txt.setValue("Test" + i);
tmp.setValue(txt);
}
}
- 更复杂的版本,我认为可以简化它 更多,但对我来说它有效:)
private static void textboxMailMerge(final MainDocumentPart document, final int nrOfSheets)
{
final String xpath = "//wps:txbx/w:txbxContent/w:p/w:r/w:t";
try
{
final List<Object> textList = document.getJAXBNodesViaXPath(xpath, false);
final int dataSize = data.size();
final int listSize = textList.size();
// check if the size is valid
if (dataSize == listSize / nrOfSheets)
{
// iterate over every text element
for (int i = 0; i < listSize; i++)
{
// iterate over every map
for (final Map<DataFieldName, String> map : data)
{
// iterate over every value from the map
for (final String value : map.values())
{
@SuppressWarnings("unchecked")
final JAXBElement<Text> element = (JAXBElement<Text>) textList.get(i);
final Text txt = new Text();
txt.setValue(value);
element.setValue(txt);
}
}
}
}
else
{
System.err.println("The size of the text elements isn't equal with the size of the data map!");
}
}
catch (XPathBinderAssociationIsPartialException | JAXBException e)
{
e.printStackTrace();
}
}
这是数据变量的样子:List<Map<DataFieldName, String>> data
所以复杂版本只是遍历所有数据并用给定文本替换合并字段的文本对象,合并字段的名称将保持不变。
我明天会测试复杂的版本,但我认为它应该可以:)
唯一让我有点恼火的是未经检查的投射警告,也许有人知道如何解决这个问题。我看了一些问题,espacilly this one is quite popular 但我不明白我在这里做错了什么。