RTFTemplate巧妙的利用了word的“域”和书签实现了报表模板的设计。通过向rtf插入特定类型的域字段,用来标记将会被报表引擎用实际数据或者逻辑替代的部分。
通常单一的值用:Mergefield代替。
循环和条件采用:bookmark替代。
RefTemplate底层采用了两类模板引擎:
第一类:Jakarta Velocity
第二类:freemark
采用的模板不同生成的中间模板文件略有差异(这是必然)。
设计模板的方法:
1. 编辑好报表模板。
2. 如果是单一值得话插入域。
MS Word207 插入域的方法:
插入->文档部件->域à类别选择(邮件合并、或者不选)->域名(F)选择“MergeField”->域属性中域名输入” $Attribute.field” (注:Attribute为填充对象名,field是其属性,注意大小写长度不宜太长)。
插入后手动删除域两边的和(如:),如果不删除的话中文显示会有一定的编码问题。
1. 循环
在循环开始及结束位置分别插入bookmark 命名分别为:START_LOOP_{i},END_LOOP_{i},循环的开始和结束需要相呼应。引擎会自动根据循环内部的内容去变量循序对象。
4. 条件
在条件对应位置插入bookmark命名为IF_{i},ELSE_{i},ENDIF_{i}.
5. MS Word207 插入书签的方法:
插入->书签
6. 对于表格不需要我们在外层加入循环,rtftemplate会根据模板和数据智能解析。
7. 完成上述的定义操作即可实现抽象类AbstractRTFUseCase类的
protectedvoidputContext(IContext context) 方法(数据填充方法)
通过调用run(模板文件)即可将文件将模板和数据相结合,产生最终结果。
我们可以通过该类的:saveTransformedDocument(true)方法设定是否输出模板转换结果该结果文件用来核对产出物是否正确。(实际上最终生成是调用freemark 或者Jakarta Velocity生成的)。
我们可以通过saveXmlFields(xmlFieldsAvailable, false);来设定是否输出xml文件用来核对相关字段是否解释正确。
8.至此我们就完成了rtf报表输出的相关工作。
该引擎的缺陷:
该引擎每次都会由rtf模板文件生成最终引擎所需要的模板文件。运行效率低下,且存在转换结果不一致的现象。对于相对简单的报表该引擎基本满足,但是循环嵌套较多的模板实测结果不是很理想。
在实际项目中笔者通过了rtftemplate 生成了模板文件的初步rtf模板,在文本编辑器中调整了相关的属性后直接采用了使用freemark加载模板的方式,生成了报表。实际测试运行情况稳定。在该方式中rtftemplate充当了报表设计器的角色。
使用RTFtemplate直接加载freemark模板范例:
定义如下类:
public class RTFGenerator {
private Map<String, Object> contextMap;
private RTFTemplate rtfTemplate = null;
private File transformerConfigFile = null;
private ApplicationContext applicationContext;
private String rtfTemplateImpl;
private boolean saveTransformedDocument = false;
private String outDirectory = null;
/**
* This value allow to group by content when there is PageBreak in order to
* group by content.
*/
private int groupByPerPageBreak = -1;
// true if context has circular references and false otherwise
private boolean circularReferences = false;
public RTFGenerator(String outDirectory,Map<String, Object> contextMap) {
this.outDirectory = outDirectory;
this.contextMap = contextMap;
}
/**
* Run RTFTemplate for merging rtfSource with the context putted with the
* method putContext which be must implement. After execution of this
* method, files rtfSource + ".<rtfTemplateImpl>.rtf" (RTF template
* implementation (vmRTFtemplate,...) and rtfSource + ".out.rtf" (RTF final
* with values of the context) will be generate.
*
* @param rtfSource
* RTF source model.
* @throws Exception
*/
public final void run(String rtfSource, String rtfTagert) throws Exception {
File rtfSourceFile = new File(rtfSource);
String rtfTransformedDocumentOutput = rtfSource + "."
+ getRtfTemplateImpl() + ".rtf";
//String rtfOutput = rtfSource + "." + getRtfTemplateImpl() + ".out.rtf";
String rtfOutput = rtfTagert;
rtfTransformedDocumentOutput = rtfTagert + "_temp.rtf";
if (outDirectory != null) {
// Create out Directory
File out = new File(outDirectory);
out.mkdirs();
/*
rtfTransformedDocumentOutput = outDirectory + "/"
+ rtfSourceFile.getName() + "." + getRtfTemplateImpl()
+ ".rtf";
rtfOutput = outDirectory + "/" + rtfSourceFile.getName() + "."
+ getRtfTemplateImpl() + ".out.rtf";
*/
}
/**
* 1. Get RTFtemplate builder
*/
RTFTemplateBuilder builder = null;
if (applicationContext == null)
builder = RTFTemplateBuilder.newRTFTemplateBuilder();
else
builder = RTFTemplateBuilder
.newRTFTemplateBuilder(applicationContext);
/**
* 2. Get RTFtemplate with Implementation
*/
this.rtfTemplate = builder.newRTFTemplate(rtfTemplateImpl);
this.rtfTemplate.setGroupByPerPageBreak(groupByPerPageBreak);
this.rtfTemplate.setCircularReferences(circularReferences);
/**
* 3. Put default format
*/
putDefaultFormat(rtfTemplate);
// VelocityTemplateEngineImpl templateEngine = new VelocityTemplateEngineImpl();
// VelocityEngine velocityEngine = new VelocityEngine();
// velocityEngine.setProperty("input.encoding ", "UTF-8");
// velocityEngine.setProperty("output.encoding", "GBK");
// velocityEngine.setProperty ("response.encoding", "GBK-8");
// templateEngine.setVelocityEngine(velocityEngine);
FreemarkerTemplateEngineImpl templateEngine = new FreemarkerTemplateEngineImpl();
rtfTemplate.setTemplateEngine(templateEngine);
/**
* 4. Create a common inner context - not required but showing how
* common context values can be re-used
*/
IContext ctx = rtfTemplate.getTemplateEngine().newContext();
putGlobalContext(ctx);
/**
* 5. Set the template
*/
rtfTemplate.setTemplate(rtfSourceFile);
/**
* 6. Set Global Context
*/
rtfTemplate.setGlobalContext(ctx);
/**
* 7. Set Transformer Config
*/
if (transformerConfigFile != null) {
TransformerConfig transformConfig = DigesterTransformerConfig
.getTransformerConfig(new FileInputStream(
transformerConfigFile));
rtfTemplate.setTransformerConfig(transformConfig);
}
/**
* 8. Put Context
*/
putContext(rtfTemplate.getContext());
if (saveTransformedDocument) {
RTFDocument transformedDocument = rtfTemplate.transform();
transformedDocument.save(new File(rtfTransformedDocumentOutput));
}
/**
* 9. Merge template and context
*/
rtfTemplate.merge(rtfOutput);
}
/**
* Return String XML Mergefields used in your context and Bookmarks (for
* start and end loop)
*
* @return
*/
public String getXMLFields() {
// XML
RTFXmlFieldsReader reader = new RTFXmlFieldsReader();
reader.readContext(rtfTemplate.getContext(), rtfTemplate
.getTransformerConfig(), rtfTemplate.isCircularReferences());
return reader.getXMLFields();
}
protected void putDefaultFormat(RTFTemplate template) {
}
protected void putGlobalContext(IContext context) {
}
/**
* Save XML fields available into file. If force parameter is false, the
* file is updated with new context (by keeping just description) otherwise
* the file is crushed with new context.
*
* @param filename
* @throws Exception
*/
public void saveXmlFields(String filename, boolean force) throws Exception {
RTFContextFieldsReader reader = new RTFContextFieldsReader();
reader.readContext(rtfTemplate.getContext(), rtfTemplate
.getTransformerConfig(), rtfTemplate.isCircularReferences());
RTFContextUtil
.saveXmlFields(filename, reader.getContextFields(), force);
}
/**
* This method must be implement by class wich manage your RTF model. Put
* the context of your model (eg : context("date", new Date()); )
*
* @param context
* IContext
*/
protected void putContext(IContext context){
for (String key : contextMap.keySet()) {
context.put(key, contextMap.get(key));
}
}
public void setTransformerConfigFile(String transformerConfig) {
setTransformerConfigFile(new File(transformerConfig));
}
public void setTransformerConfigFile(File transformerConfigFile) {
this.transformerConfigFile = transformerConfigFile;
}
/**
* set true if RTF with (velocity, freemarker,... macro) file must be
* generated and false otherwise.
*
* @param saveTransformedDocument
*/
public void saveTransformedDocument(boolean saveTransformedDocument) {
this.saveTransformedDocument = saveTransformedDocument;
}
public String getRtfTemplateImpl() {
if (rtfTemplateImpl == null) {
/**
* Default RTFTemplate is Velocity
*/
//this.rtfTemplateImpl = RTFTemplateBuilder.DEFAULT_VELOCITY_RTFTEMPLATE;
this.rtfTemplateImpl = RTFTemplateBuilder.DEFAULT_FREEMARKER_RTFTEMPLATE;
}
return rtfTemplateImpl;
}
public void setRtfTemplateImpl(String rtfTemplateImpl) {
this.rtfTemplateImpl = rtfTemplateImpl;
}
protected int getGroupByPerPageBreak() {
return groupByPerPageBreak;
}
/**
* This value allow to group by content when there is PageBreak in order to
* group by content.
*
* @param groupByPerPageBreak
*/
protected void setGroupByPerPageBreak(int groupByPerPageBreak) {
this.groupByPerPageBreak = groupByPerPageBreak;
}
protected RTFTemplate getRtfTemplate() {
return rtfTemplate;
}
protected void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
public boolean isCircularReferences() {
return circularReferences;
}
/**
* true if context has circular references and false otherwise
* @param circularReferences
*/
public void setCircularReferences(boolean circularReferences) {
this.circularReferences = circularReferences;
}
}
调用代码:
Map<String, Object> contextMap = new HashMap<String, Object>();
UseCaseLog logger = new UseCaseLog();
logger.logTitle("Start export report...");
// Create out Directory
// Generate RTF file by using RTF model table.rtf
//String rtfSource = SpringContextHolder.getRootRealPath() + "/report/template/proi.rtf";
String rtfSource = SpringContextHolder.getRootRealPath() + "/report/template/rtf_temp.rtf";
String rtfTarget = SpringContextHolder.getRootRealPath() + "/report/" + saveName +".rtf";
String outputPath = SpringContextHolder.getRootRealPath() + "/report/";
System.out.println(rtfSource);
System.out.println(rtfTarget);
System.out.println(outputPath);
putContextVal(contextMap,projectId,reportName);
RTFGenerator usecase = new RTFGenerator(outputPath, contextMap);
/**
* Set Velocity as RTFTemplate implementation
*
*/
// usecase.setRtfTemplateImpl(RTFTemplateHelper.DEFAULT_VELOCITY_RTFTEMPLATE);
// => this line is
// not required, because by default velocity is the default RTFTemplate
// implementation
usecase.saveTransformedDocument(false); // Save RTF file with velocity
// macro
usecase.run(rtfSource, rtfTarget);
logger.logTitle("End export report!");
private void putContextVal(Map<String, Object> context ,String projectId,String reportName) {
// TODO Auto-generated method stub
// 项目基本信息
getProjectBaseInfo(context, projectId,reportName);
}
注意事项:
1. 中文编码问题
a) 中文输出乱码时在填充context时对String内容进行编码
核心方法:
public class GBKEncoded {
public static String getEncodedRTFString(String sourceStr) {
try{
return java.net.URLEncoder.encode(sourceStr, "GBK").replaceAll("%", "\\\\'").replaceAll("\\+", " ");
}catch(Exception ex){
return sourceStr;
}
}
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
}
}
2. 图片处理
a) 图片需转换为rtf对应格式的字符串转换
核心方法如下:
package net.sourceforge.rtf.format;
import java.io.InputStream;
import java.text.FieldPosition;
import java.text.Format;
import java.text.ParsePosition;
import net.sourceforge.rtf.context.image.FormatBase;
import net.sourceforge.rtf.context.image.ImageConstants;
import org.apache.commons.io.IOUtils;
/**
* Description : Format for Input Stream.
* @version 1.0.0
* @author <a href="mailto:angelo.zerr@gmail.com">Angelo ZERR</a>
*/
public class DefaultInputStreamFormat extends Format {
public static final long serialVersionUID = 1L;
public StringBuffer format(Object arg0, StringBuffer arg1, FieldPosition arg2) {
InputStream in = (InputStream) arg0;
byte[] imagedata = null;
try {
imagedata = IOUtils.toByteArray(in);
}
catch (Exception e) {
} finally {
IOUtils.closeQuietly(in);
}
StringBuffer buf = new StringBuffer("");
if (imagedata != null) {
// Test if image date is image
FormatBase imageformat = FormatBase.determineFormat(imagedata);
if (!(imageformat.getType() == ImageConstants.I_NOT_SUPPORTED
| imageformat.getRtfTag() == "")) {
buf = new StringBuffer(imagedata.length * 3);
for (int i = 0; i < imagedata.length; i++) {
int iData = imagedata [i];
// Make positive byte
if (iData < 0) {
iData += 256;
}
if (iData < 16) {
// Set leading zero and append
buf.append('0');
}
buf.append(Integer.toHexString(iData));
}
buf.insert(0, "{\\*\\shppict{\\pict\\" + imageformat.getRtfTag() + " ");
buf.append("}");
buf.append("}");
}
}
return buf;
}
public Object parseObject(String arg0, ParsePosition arg1) {
// TODO Auto-generated method stub
return null;
}
}