摘要:這幾天遇到個需求,需要提供用戶下載電子證照,最簡單的方法實現:word做了一份模板,利用網頁工具轉成OFD文件,http://www.yozodcs.com/page/example.html用7Z工具解壓ofd文件,壓縮使用WinZip,壓縮的時候不能帶根路徑;得到文件目錄結構如下:
首先我們來看OFD文件的目錄結構,OFD文件底層為XML,解析可查看相關文檔,下面簡述我了解的:
Doc_1文件夾下面是主要內容
Pages下面存放OFD每個頁面的XML
簡單說明Pages下面頁面信息XML文件的標簽及屬性作用
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <ofd:Page xmlns:ofd="http://www.ofdspec.org/2016"> <ofd:Area> <ofd:PhysicalBox>0 0 209.90291 296.68628</ofd:PhysicalBox> <ofd:ApplicationBox>0 0 209.90291 296.68628</ofd:ApplicationBox> <ofd:ContentBox>0 0 209.90291 296.68628</ofd:ContentBox> </ofd:Area> <ofd:Content> <ofd:Layer ID="0"> <ofd:ImageObject ID="7" ResourceID="6" Boundary="0 -0.3528 210.2557 297.0391" CTM="210.2557 0 0 297.0391 0 0"/> <ofd:TextObject ID="9" Font="8" Size="7.761115550994873" Boundary="47.2723 67.0278 261.0557 15.5222"> <ofd:CGTransform CodePosition="0" CodeCount="17" GlyphCount="17"> <ofd:Glyphs>11738 12862 7255 17457 12235 14764 13842 11859 12078 11931 15952 14729 11465 7256 11113 11465 13610</ofd:Glyphs> </ofd:CGTransform> <ofd:TextCode X="0.0" Y="7.7611" DeltaX="7.7611 7.4083 7.4083 7.7611 7.7611 7.7611 7.7611 7.7611 7.7611 7.7611 7.7611 7.7611 8.1139 7.0556 7.7611 7.7611">填充字符填充字符填充字符填充字符填</ofd:TextCode> </ofd:TextObject> </ofd:Layer> </ofd:Content> </ofd:Page>
ofd:CGTransform:標簽內的內容,為網站生成模板內,Res文件夾下的字體文件內的檢索碼,這里重點說明,我們通過網站得到的模板文件,在res文件加下面存放的字體文件只包含你OFD文件內存在的文字,所以你會發現這個字體文件特別小,你需要到你電腦的C:\Windows\Fonts下面找到你需要的字體文件,或者自己准備也行,這樣可以保證OFD文件在各種環境下查看,打印字體的統一!!!然后在PublicRes.xml文件內設置你自己准備的字體,(自己准備的字體需要放到Res文件夾下面),ofd:CGTransform標簽內存在內容(字符碼),則去字體文件內尋找匹配的文字,沒有則顯示ofd:TextCode標簽內的內容。這我選擇使用ofd:TextCode顯示內容,ofd:CGTransform標簽則全部刪掉。
字體文件設置文件 PublicRes.xml 如下
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <ofd:Res BaseLoc="Res" xmlns:ofd="http://www.ofdspec.org/2016"> <ofd:Fonts> <ofd:Font ID="" FontName="CBMOWR+STKaiti" FamilyName="CBMOWR+STKaiti"> <ofd:FontFile>font_1.otf</ofd:FontFile> </ofd:Font> <ofd:Font ID="8" FontName="STKaiti" FamilyName="STKaiti"> <FontFile>font.TTF</FontFile> </ofd:Font> </ofd:Fonts> </ofd:Res>
注意 ofd:Font 標簽內的ID屬性,頁面信息XML文件內 ofd:TextObject 標簽的font屬性和此處的id一致即可引用字體。至於ofd:Font標簽內的FontName屬性則需要填入字體正確
的頭文件信息內的名稱.
重點說明標簽 ofd:TextObject
ofd:TextObject 標簽下的 Boundary 屬性:將ofd:TextObject看作一個box,前兩個值分別為定位點的坐標,后兩個值為X軸和Y軸相對與該定位點的偏移量,ofd:TextObject標簽顯示內容位置與XML文件內的順序無關
ofd:TextCode 標簽下的 DeltaX 屬性:為字符X軸偏移量,顯示的內容有10個,偏移量最少得設置9個,如果顯示內容字符數-偏移量數 > 1,則會發生字符重疊現象;
使用dom4j來操作xml文件,測試代碼-->
package code; import java.awt.Font; import java.awt.FontFormatException; import java.io.File; import java.io.FileInputStream; import java.io.FileWriter; import java.io.IOException; import java.io.ObjectInputStream.GetField; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.poi.xslf.model.geom.Path; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.DocumentHelper; import org.dom4j.Element; import org.dom4j.Node; import org.dom4j.XPath; import org.dom4j.io.OutputFormat; import org.dom4j.io.SAXReader; import org.dom4j.io.XMLWriter; import util.OprationFile; import util.ZipUtil; public class CreateElectronicCertification { //ofd文件迭代id,不能重復,數據折行新增節點設置id private static int index; public static void main(String[] args) throws DocumentException, IOException { Map<String, Object> map = new HashMap<String, Object>(); generateOFD(map); //OprationFile.deleteFiles(new File(newOFDFileSys));//刪除生成的文件。這里自己操作參數,備份好文件,刪除不可恢復, } public static boolean generateOFD(Map<String,Object> map) throws IOException, DocumentException{ //ofd文件模板文件夾位置 String resourcesAddress = "E:/OFD_model";//map.get("resourcesAddress"); //不折行最大字符長度 int lineWide = 19;//map.get("lineWide"); //強制換行符 String lineBreak = "/r";//map.get("lineBreak"); //折行Y軸偏移量 double Y_offset = 7.7611;//map.get("Y_offset"); //生成OFD文件存放文件夾 String OFDAddress = "E:/";//map.get("OFDAddress"); String modelForIDFileAddress = "/Doc_1/Document.xml";//map.get("modelForIDFileAddress"); //String newFileName = Common.createGUID(); String newFileName = "OFD";//隨機OFD文件名 String maxIdAndPagesFile = modelForIDFileAddress;//OFD模板存放MaxID,pageAddress地址的Document位置 String newOFDFileSys = copyModelFileSys(newFileName,resourcesAddress);//拷貝模板文件,返回新文件路徑 if(newOFDFileSys.equals("0")){ return false; } getMaxId(newOFDFileSys+maxIdAndPagesFile); //獲取頁面xml文件的地址 List<String> pageAddressList = getPagesAddress(newOFDFileSys+maxIdAndPagesFile); for(String pageAddress: pageAddressList){ pageAddress = newOFDFileSys+"/Doc_1/"+pageAddress; oprationXmlForDom4j(pageAddress, map, lineBreak, lineWide, Y_offset); } String OFDName = newFileName+".ofd"; //電子簽章,二維碼等圖片在此覆蓋模板內圖片 //save(String str); //去根目錄壓縮(OFD文件壓縮不能帶根目錄) ZipUtil.doCompress(newOFDFileSys, OFDAddress+OFDName,newFileName); return true; } //獲取page頁面存放地址 public static List<String> getPagesAddress(String maxIdAndPagesFile) throws DocumentException{ SAXReader reader = new SAXReader(); Document document = reader.read(new File(maxIdAndPagesFile)); List<Element> list =document.selectNodes("//ofd:Page");//XPath List<String> pageAddressList = new ArrayList<String>(); for(Element elment:list){ pageAddressList.add(elment.attributeValue("BaseLoc")); } pageAddressList.remove(0); return pageAddressList; } public static String copyModelFileSys(String newFileName,String resourcesAddress) throws IOException{ String oldFileName = new File(resourcesAddress).getName(); String newOFDFileSys = resourcesAddress.replaceAll(oldFileName, newFileName); if(OprationFile.copyDir(resourcesAddress, newOFDFileSys)){ return newOFDFileSys; } return "0"; } //獲取最大id,OFD文件,XML內的ID屬性類似前端頁面的元素id,不可重復,這里取最大的在有新增節點時設置id用 public static void getMaxId(String address) throws DocumentException{ SAXReader reader = new SAXReader(); Document document = reader.read(new File(address)); @SuppressWarnings("unchecked") List<Node>list=document.selectNodes("//ofd:MaxUnitID"); index = Integer.parseInt(list.get(0).getText())+1; } public static void oprationXmlForDom4j(String fileAddress,Map<String, Object> map,String lineBreak,int lineWide,double Y_offset) throws DocumentException, IOException { File file = new File(fileAddress); SAXReader reader = new SAXReader(); Document document = reader.read(file); Element root = document.getRootElement(); Element layerElement = root.element("Content").element("Layer"); Iterator iterator = document.selectNodes("//ofd:TextObject").iterator(); List<Element> foldLineLsit = new ArrayList<Element>(); while (iterator.hasNext()) { Element textObjectElement = (Element) iterator.next(); for(String key : map.keySet()){ if((textObjectElement.element("TextCode").getText()).equals(key)){ if(key.equals("fj")){ lineWide = 27;//附記頁最大行長度 } //-------------------------------------- System.out.println("匹配參數:"+textObjectElement.element("TextCode").getText()); List<String> paramList = new ArrayList<String>(); String boundary= textObjectElement.attributeValue("Boundary"); String[] b_split = boundary.split(" "); String boundaryStr = ""; Double at_Y_offset = Double.parseDouble(b_split[1]); String params = ((String)map.get(key)).trim(); String[] paramGroup = params.split(lineBreak); //自動折行 for(int i = 0 ; i<paramGroup.length ; i++){ if(paramGroup[i].length()>lineWide){ int flag = 0; int g_len = (paramGroup[i].length()+lineWide-1)/lineWide; for(int li = 0;li<g_len;li++){ if(li+1 == g_len){ paramList.add(paramGroup[i].substring(flag,paramGroup[i].length())); }else{ paramList.add((paramGroup[i].substring(flag,flag+lineWide))); } flag+=lineWide; } }else{ paramList.add(paramGroup[i]); } } //配置Y軸偏移量,首行直接替換參數,折行依次添加節點() for(int i = 0; i < paramList.size(); i++){ String param = paramList.get(i); if(i==0){ Element atElem = textObjectElement.element("TextCode"); atElem.setText(param); }else{ at_Y_offset = Double.parseDouble(String.format("%.4f", at_Y_offset+Y_offset)); boundaryStr = b_split[0]+" "+ at_Y_offset + " " + b_split[2]+ " "+b_split[3]; //setParam(textObjectElement,param,boundaryStr); Element newElement = (Element) textObjectElement.clone();//此處必須使用clone()方法,底層調用Object的clone(); Element elem = newElement.element("TextCode"); //第三頁模板平均折行Y軸偏移量7.7611,Boundary第三,四個參數可忽略不設置,文件解析根據DeltaX偏移量自動布局Boundary寬度,高度為定值不用修改 index = index+1; elem.setText(param); newElement.attribute("ID").setValue(index+""); newElement.attribute("Boundary").setValue(boundaryStr.trim()); foldLineLsit.add(newElement); } } } } } if(foldLineLsit.size()>0){ for(Element e : foldLineLsit){ layerElement.add((Element)e.clone()); } } save(document, fileAddress); foldLineLsit.clear(); } public static void save(Document doc, String address) throws IOException { OutputFormat format = OutputFormat.createPrettyPrint(); format.setEncoding("UTF-8"); File file=new File(address); file.getParentFile().mkdirs(); XMLWriter writer = new XMLWriter(new FileWriter(file), format); writer.write(doc); writer.close(); } }
工具類
package util; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.util.Scanner; public class OprationFile { @SuppressWarnings("static-access") public static boolean copyDir(String sourcePath, String newPath) throws IOException { File file = new File(sourcePath); String[] filePath = file.list(); if (!(new File(newPath)).exists()) { (new File(newPath)).mkdir(); } for (int i = 0; i < filePath.length; i++) { if ((new File(sourcePath + file.separator + filePath[i])) .isDirectory()) { copyDir(sourcePath + file.separator + filePath[i], newPath + file.separator + filePath[i]); } if (new File(sourcePath + file.separator + filePath[i]).isFile()) { copyFile(sourcePath + file.separator + filePath[i], newPath + file.separator + filePath[i]); } } return true; } public static void copyFile(String oldPath, String newPath) throws IOException { File oldFile = new File(oldPath); File file = new File(newPath); FileInputStream in = new FileInputStream(oldFile); FileOutputStream out = new FileOutputStream(file);; byte[] buffer=new byte[2097152]; int readByte = 0; while((readByte = in.read(buffer)) != -1){ out.write(buffer, 0, readByte); } in.close(); out.close(); } public static void deleteFiles(File file){ if (file.isDirectory()) { File[] files=file.listFiles(); for (int i = 0; i < files.length; i++) { if (files[i].isDirectory()) { deleteFiles(files[i]); }else { files[i].delete(); } } } file.delete(); } public static void main(String[] args) throws IOException { Scanner sc = new Scanner(System.in); String filePath = sc.nextLine(); deleteFiles(new File(filePath)); } }
壓縮采用ZIP
package util; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; public class ZipUtil { private ZipUtil() { } public static void doCompress(String srcFile, String zipFile,String rootdDirectory) throws IOException { doCompress(new File(srcFile), new File(zipFile),rootdDirectory); } /** * 文件壓縮 * * @param srcFile * 目錄或者單個文件 * @param zipFile * 壓縮后的ZIP文件 */ public static void doCompress(File srcFile, File zipFile,String rootdDirectory) throws IOException { ZipOutputStream out = null; try { out = new ZipOutputStream(new FileOutputStream(zipFile)); doCompress(srcFile, out,rootdDirectory); } catch (Exception e) { throw e; } finally { out.close();// 記得關閉資源 } } public static void doCompress(File file, ZipOutputStream out,String rootdDirectory) throws IOException { doCompress(file, out, "",rootdDirectory); } public static void doCompress(File inFile, ZipOutputStream out, String dir,String rootdDirectory) throws IOException { if (inFile.isDirectory()) { File[] files = inFile.listFiles(); if (files != null && files.length > 0) { for (File file : files) { String name = inFile.getName(); if (!"".equals(dir)) { name = dir + "/" + name; } ZipUtil.doCompress(file, out, name,rootdDirectory); } } } else {
//將根目錄干掉,否則無法打開OFD文件 dir = dir.replaceAll(rootdDirectory, ""); ZipUtil.doZip(inFile, out, dir); } } public static void doZip(File inFile, ZipOutputStream out, String dir) throws IOException { String entryName = null; if (!"".equals(dir)) { entryName = dir + "/" + inFile.getName(); } else { entryName = inFile.getName(); } ZipEntry entry = new ZipEntry(entryName); out.putNextEntry(entry); int len = 0; byte[] buffer = new byte[1024]; FileInputStream fis = new FileInputStream(inFile); while ((len = fis.read(buffer)) > 0) { out.write(buffer, 0, len); out.flush(); } out.closeEntry(); fis.close(); } public static void main(String[] args) throws IOException { String rootdDirectory = "OFD"; doCompress("E:/OFD/", "E:/OFD.ofd",rootdDirectory); } }