利用RTFtemplate生成rtf報表
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,
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:[email protected]">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;
}
}