All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.ofdrw.layout.OFDDoc Maven / Gradle / Ivy

The newest version!
package org.ofdrw.layout;

import org.dom4j.DocumentException;
import org.ofdrw.core.attachment.Attachments;
import org.ofdrw.core.attachment.CT_Attachment;
import org.ofdrw.core.basicStructure.doc.CT_CommonData;
import org.ofdrw.core.basicStructure.doc.Document;
import org.ofdrw.core.basicStructure.ofd.DocBody;
import org.ofdrw.core.basicStructure.ofd.OFD;
import org.ofdrw.core.basicStructure.ofd.docInfo.CT_DocInfo;
import org.ofdrw.core.basicStructure.pageObj.Page;
import org.ofdrw.core.basicStructure.pageTree.Pages;
import org.ofdrw.core.basicType.ST_ID;
import org.ofdrw.core.basicType.ST_Loc;
import org.ofdrw.gv.GlobalVar;
import org.ofdrw.layout.edit.AdditionVPage;
import org.ofdrw.layout.edit.Annotation;
import org.ofdrw.layout.edit.AnnotationRender;
import org.ofdrw.layout.edit.Attachment;
import org.ofdrw.layout.element.Div;
import org.ofdrw.layout.edit.Watermark;
import org.ofdrw.layout.engine.*;
import org.ofdrw.layout.engine.render.RenderException;
import org.ofdrw.layout.exception.DocReadException;
import org.ofdrw.layout.handler.RenderFinishHandler;
import org.ofdrw.layout.handler.VPageHandler;
import org.ofdrw.pkg.container.DocDir;
import org.ofdrw.pkg.container.OFDDir;
import org.ofdrw.pkg.container.VirtualContainer;
import org.ofdrw.reader.OFDReader;
import org.ofdrw.reader.PageInfo;
import org.ofdrw.reader.ResourceLocator;

import java.io.Closeable;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Virtual Document 虚拟文档对象
 * 

* 与 {@link org.ofdrw.core.basicStructure.doc.Document} 区别 *

* 使用API的方式构造OFD文档,并打包为OFD文件。 * * @author 权观宇 * @since 2020-3-17 20:13:51 */ public class OFDDoc implements Closeable { /** * 已有的OFD文档解析器 *

* 仅在修改的模式有效 */ private OFDReader reader; /** * OFD 打包 */ private OFDDir ofdDir; /** * 打包后OFD文档存放路径 * outPath/outStream,二选一 */ private Path outPath; /** * 打包后OFD文档输出流 * outPath/outStream,二选一 */ private OutputStream outStream; /** * 当前文档中所有对象使用标识的最大值。 * 初始值为 0。MaxUnitID主要用于文档编辑, * 在向文档增加一个新对象时,需要分配一个 * 新的标识符,新标识符取值宜为 MaxUnitID + 1, * 同时需要修改此 MaxUnitID值。 */ private AtomicInteger MaxUnitID = new AtomicInteger(0); /** * 外部资源管理器 */ ResManager prm; /** * 注释渲染器 *

* 仅在需要增加注释时进行初始化 */ private AnnotationRender annotationRender; /** * 流式布局元素队列 */ private LinkedList

streamQueue = new LinkedList<>(); /** * 固定布局虚拟页面队列 */ private LinkedList vPageList = new LinkedList<>(); /** * 流式布局集合 队列(用于编辑) */ private LinkedList sPageList = new LinkedList<>(); /** * 页面样式 *

* 默认为 A4 *

* 页边距:上下都是2.54厘米,左右都是3.17厘米。 */ private PageLayout pageLayout = PageLayout.A4(); /** * 文档属性信息,该对象会在初始化是被创建并且添加到文档中 * 此处只是保留引用,为了方便操作。 */ private CT_CommonData cdata; /** * 文档是否已经关闭 * true 表示已经关闭,false 表示未关闭 */ private boolean closed = false; /** * OFD文档对象 */ private Document ofdDocument; /** * 正在操作的文档目录 */ private DocDir operateDocDir; /** * 渲染结束时回调函数(可选) */ private RenderFinishHandler renderingEndHandler; /** * 页面解析前处理器 */ private VPageHandler onPageHandler = null; /** * 文档水印 */ private Watermark defaultWatermark = null; /** * 在指定路径位置上创建一个OFD文件 * * @param outPath OFD输出路径 */ public OFDDoc(Path outPath) { this(); if (outPath == null) { throw new IllegalArgumentException("OFD文件存储路径(outPath)为空"); } if (Files.isDirectory(outPath)) { throw new IllegalArgumentException("OFD文件存储路径(outPath)不能是目录"); } final Path parent = outPath.toAbsolutePath().getParent(); if (parent == null || !Files.exists(parent)) { throw new IllegalArgumentException("OFD文件存储路径(outPath)上级目录 [" + parent + "] 不存在"); } this.outPath = outPath; } /** * 在指定路径位置上创建一个OFD文件 * * @param outStream OFD输出流,由调用者负责关闭。 */ public OFDDoc(OutputStream outStream) { this(); if (outStream == null) { throw new IllegalArgumentException("OFD文件输出流(outStream)为空"); } this.outStream = outStream; } /** * 修改一个OFD文档 * * @param reader OFD解析器 * @param outPath 修改后文档生成位置 * @throws DocReadException 文档读取异常 */ public OFDDoc(OFDReader reader, Path outPath) throws DocReadException { if (reader == null) { throw new IllegalArgumentException("OFD解析器(reader)不能为空"); } if (outPath == null) { throw new IllegalArgumentException("OFD文件存储路径(outPath)为空"); } if (Files.isDirectory(outPath)) { throw new IllegalArgumentException("OFD文件存储路径(outPath)不能是目录"); } this.outPath = outPath; this.reader = reader; // 通过OFD解析器初始化文档对象 try { containerInit(reader); } catch (FileNotFoundException | DocumentException e) { throw new DocReadException("OFD文件解析异常", e); } } /** * 修改一个OFD文档 * * @param reader OFD解析器 * @param outStream 修改后文档输出流 * @throws DocReadException 文档读取异常 */ public OFDDoc(OFDReader reader, OutputStream outStream) throws DocReadException { if (reader == null) { throw new IllegalArgumentException("OFD解析器(reader)不能为空"); } if (outStream == null) { throw new IllegalArgumentException("OFD文件输出流(outStream)为空"); } this.outStream = outStream; this.reader = reader; // 通过OFD解析器初始化文档对象 try { containerInit(reader); } catch (FileNotFoundException | DocumentException e) { throw new DocReadException("OFD文件解析异常", e); } } /** * 文档初始化构造器 */ private OFDDoc() { // 初始化文档对象 containerInit(); } /** * 设置页面默认的样式 * * @param pageLayout 页面默认样式 * @return this */ public OFDDoc setDefaultPageLayout(PageLayout pageLayout) { if (pageLayout == null) { return this; } this.pageLayout = pageLayout; // 设置页面大小 cdata.setPageArea(pageLayout.getPageArea()); return this; } /** * 初始化OFD虚拟容器 */ private void containerInit() { CT_DocInfo docInfo = new CT_DocInfo() .setDocID(UUID.randomUUID()) .setCreationDate(LocalDate.now()) .setCreator("OFD R&W") .setCreatorVersion(GlobalVar.Version); DocBody docBody = new DocBody() .setDocInfo(docInfo) .setDocRoot(new ST_Loc("Doc_0/Document.xml")); OFD ofd = new OFD().addDocBody(docBody); // 创建一个低层次的文档对象 ofdDocument = new Document(); cdata = new CT_CommonData(); // 默认使用RGB颜色空间所以此处不设置颜色空间 // 设置页面属性 this.setDefaultPageLayout(this.pageLayout); ofdDocument.setCommonData(cdata) // 空的页面引用集合,该集合将会在解析虚拟页面时得到填充 .setPages(new Pages()); ofdDir = OFDDir.newOFD() .setOfd(ofd); // 创建一个新的文档 DocDir docDir = ofdDir.newDoc(); operateDocDir = docDir; docDir.setDocument(ofdDocument); prm = new ResManager(ofdDir, docDir, MaxUnitID); } /** * 通过已有文档初始化文档容器 * * @param reader OFD解析器 */ private void containerInit(OFDReader reader) throws FileNotFoundException, DocumentException { ofdDir = reader.getOFDDir(); OFD ofd = ofdDir.getOfd(); DocBody docBody = ofd.getDocBody(); CT_DocInfo docInfo = docBody.getDocInfo(); // 设置文档修改时间 docInfo.setModDate(LocalDate.now()); // 资源定位器 ResourceLocator rl = reader.getResourceLocator(); // 找到 Document.xml文件并且序列化 ST_Loc docRoot = docBody.getDocRoot(); ofdDocument = rl.get(docRoot, Document::new); // 取出文档修改前的文档最大ID cdata = ofdDocument.getCommonData(); ST_ID maxUnitID = cdata.getMaxUnitID(); // 设置当前文档最大ID MaxUnitID = new AtomicInteger(maxUnitID.getId().intValue()); operateDocDir = ofdDir.obtainDocDefault(); prm = new ResManager(ofdDir, ofdDir.obtainDocDefault(), MaxUnitID); } /** * 向文档中加入元素 *

* 适合于流式布局 * * @param item 元素 * @return this */ public OFDDoc add(Div item) { if (streamQueue.contains(item)) { throw new IllegalArgumentException("元素已经存在,请重复放入"); } streamQueue.add(item); return this; } /** * 向文档中加入虚拟页面 *

* 适合于固定布局 * * @param virtualPage 虚拟页面 * @return this */ public OFDDoc addVPage(VirtualPage virtualPage) { vPageList.add(virtualPage); return this; } /** * 向文档中加入虚拟页面 *

* 适合编辑时,添加流式的内容 * * @param streamCollect 流式页面 * @return this */ public OFDDoc addStreamCollect(StreamCollect streamCollect) { sPageList.add(streamCollect); return this; } /** * 获取指定页面追加页面对象 *

* 并且追加到虚拟页面列表中 * * @param pageNum 页码,从1起。 * @return 追加页面对象 */ public AdditionVPage getAVPage(int pageNum) { if (reader == null) { throw new RuntimeException("仅在修改模式下允许获取追加页面对象(AdditionVPage)"); } // 获取页面的OFD对象 ST_Loc pageAbsLoc = reader.getPageAbsLoc(pageNum); ResourceLocator rl = reader.getResourceLocator(); try { Page page = rl.get(pageAbsLoc, Page::new); // 构造追加页面对象 AdditionVPage avp = new AdditionVPage(page, pageAbsLoc); avp.setPageNum(pageNum); // 自动加入到虚拟页面列表中 this.addVPage(avp); return avp; } catch (FileNotFoundException | DocumentException e) { throw new RuntimeException("OFD解析失败,原因:" + e.getMessage(), e); } } /** * 向页面中增加注释对象 * * @param pageNum 页码 * @param annotation 注释对象 * @return this * @throws IOException 文件操作异常 * @throws RenderException 渲染异常 */ public OFDDoc addAnnotation(int pageNum, Annotation annotation) throws IOException { if (annotation == null) { return this; } if (reader == null) { throw new RuntimeException("仅在修改模式下允许获取追加注释对象,请使用reader构造"); } if (annotationRender == null) { annotationRender = new AnnotationRender(reader.getOFDDir().obtainDocDefault(), prm, MaxUnitID); } // 获取页面信息 PageInfo pageInfo = reader.getPageInfo(pageNum); // 渲染注释内容 annotationRender.render(pageInfo, annotation); return this; } /** * 获取页面样式(只读) *

* 如果需要重新设置默认的页面样式那么请使用 {@link #setDefaultPageLayout} * * @return 页面样式(只读) */ public PageLayout getPageLayout() { return pageLayout.clone(); } /** * 向文档中添加附件文件 *

* 如果名称相同原有附件将会被替换,附件文件将被放置于文档的默认资源目录下"/Doc_0/Res/"。 * * @param attachment 附件文件对象 * @return this * @throws IOException 文件操作异常 */ public OFDDoc addAttachment(Attachment attachment) throws IOException { if (attachment == null) { return this; } DocDir docDefault = ofdDir.obtainDocDefault(); // 若Res不存在则创建 String resAbsPath = docDefault.obtainRes().getAbsLoc().toString(); return this.addAttachment(resAbsPath, attachment); } /** * 向文档中添加附件文件 *

* 如果名称相同原有附件将会被替换,附件文件将被放置于指定目录下。 * * @param absPath 附件在OFD容器内的绝对位置,若不存在则创建,例如 "/Doc_0/Res/ * @param attachment 附件文件对象 * @return this * @throws IOException 文件操作异常 */ public OFDDoc addAttachment(String absPath, Attachment attachment) throws IOException { if (attachment == null) { return this; } if (absPath == null || absPath.startsWith("/") == false) { throw new IllegalArgumentException("附件在OFD容器内的绝对位置(absPath)不能为空或不合法"); } Path file = attachment.getFile(); if (file == null || Files.notExists(file)) { return null; } DocDir docDefault = ofdDir.obtainDocDefault(); // 构造完整路径 ST_Loc fileAbsLoc = new ST_Loc(absPath).cat(file.getFileName().toString()); // 将文件放置于指定目录下 fileAbsLoc = ofdDir.putFileAbs(fileAbsLoc.toString(), file); // 计算附件所占用的空间,单位KB。 double size = Files.size(file) / 1024d; CT_Attachment ctAttachment = attachment.getAttachment() .setID(String.valueOf(MaxUnitID.incrementAndGet())) .setCreationDate(LocalDateTime.now()) .setSize(size) .setFileLoc(fileAbsLoc); ResourceLocator rl = new ResourceLocator(docDefault); // 获取附件目录,并切换目录到与附件列表文件同级 Attachments attachments = obtainAttachments(docDefault, rl); // 清理已经存在的同名附件 cleanOldAttachment(rl, attachments, attachment.getName()); // 加入附件记录 attachments.addAttachment(ctAttachment); return this; } /** * 给整个文档增加水印. * 本方法会遍历整个文档,将水印添加到每个页面中。 * 按照OFD标准 GB/T 33190-2016 15.2 分页注释文件 的设计水印应该以一种特殊的注释类型写入。 * 如果要添加图片类型的水印,请使用 {@link Annotation}, 参考:{@link #addAnnotation(int, Annotation)} * 如果要为指定页码添加水印,请使用 {@link #addAnnotation(int, Annotation)} * @param watermark 水印信息 * @throws IOException 文件操作异常 * @return this */ public OFDDoc addWatermark(Watermark watermark) throws IOException { if (watermark == null) { return this; } if (reader == null) { throw new RuntimeException("仅在修改模式下允许获取追加注释对象,请使用reader构造"); } int pageNums = this.reader.getNumberOfPages(); if(pageNums == 0){ return this; } for(int i = 1; i <= pageNums; i++) { addAnnotation(i, watermark); } return this; } /** * 删除指定名称的附件 * * @param name 附件名称,若附件不存在则忽略。 * @return this * @throws IOException 文件操作异常 */ public OFDDoc deleteAttachment(String name) throws IOException { if (name == null || name.trim().isEmpty()) { return this; } DocDir docDefault = ofdDir.obtainDocDefault(); ResourceLocator rl = new ResourceLocator(docDefault); Attachments attachments = obtainAttachments(docDefault, rl); cleanOldAttachment(rl, attachments, name); return this; } /** * 清理已经存在的资源 * * @param rl 资源加载器 * @param attachments 附件列表 * @param name 附件名称 */ private void cleanOldAttachment(ResourceLocator rl, Attachments attachments, String name) throws IOException { final List list = attachments.getAttachments(); for (CT_Attachment att : list) { // 找到匹配的附件 if (att.getAttachmentName().equals(name)) { // 删除附件记录 attachments.remove(att); // 删除附件的文件 ST_Loc fileLoc = att.getFileLoc(); Path file = rl.getFile(fileLoc); if (file != null && Files.exists(file)) { Files.delete(file); } break; } } } /** * 获取附件列表文件,如果文件不存在则创建 *

* 该操作将会切换资源加载器到与附件文件同级的位置 * * @param rl 资源加载器 * @param docDir 文档目录 * @return 附件列表文件 */ private Attachments obtainAttachments(DocDir docDir, ResourceLocator rl) { ST_Loc attLoc = ofdDocument.getAttachments(); Attachments attachments = null; if (attLoc != null) { try { attachments = rl.get(attLoc, Attachments::new); // 切换目录到资源文件所在目录 rl.cd(attLoc.parent()); } catch (DocumentException | FileNotFoundException e) { // 忽略错误 System.err.println(">> 无法解析Attachments.xml文件,将重新创建该文件"); attachments = null; } } if (attachments == null) { attachments = new Attachments(); docDir.putObj(DocDir.Attachments, attachments); ofdDocument.setAttachments(docDir.getAbsLoc().cat(DocDir.Attachments)); } return attachments; } /** * 获取 OFD虚拟容器 *

* 通过虚拟容器API就可以直接操作XML文件和目录结构 * * @return OFD虚拟容器 */ public OFDDir getOfdDir() { return ofdDir; } /** * 获取 文档根节点 *

* 根节点中包含了文档各类信息的入口 * * @return 文档根节点 */ public Document getOfdDocument() { return ofdDocument; } /** * 当渲染结束时的回调函数 * * @param renderFinishHandler OFD渲染结束时回调函数,可以为null,不调用 * @return this */ public OFDDoc onRenderFinish(RenderFinishHandler renderFinishHandler) { this.renderingEndHandler = renderFinishHandler; return this; } /** * 返回正在编辑文档的Reader对象 *

* 若为新建文档那么该方法将会返回null * * @return 正在编辑文档的Reader对象 */ public OFDReader getReader() { return reader; } /** * 获取 资源管理器对象 *

* 通过资源管理器API就可以直接操作文档资源 * * @return OFD虚拟容器 */ public ResManager getResManager() { return prm; } /** * 获取 当前解析页面的回调 * * @return 当前解析页面的回调,可能为 null 。 */ public VPageHandler getOnPage() { return this.onPageHandler; } /** * 设置 当前解析页面的回调函数 *

* 通过回调函数可在页面变为OFD内容前向页面追加内容,例如:添加页头、添加页脚。 * * @param handler 页面解析前处理器 * @return this */ public OFDDoc onPage(VPageHandler handler) { this.onPageHandler = handler; return this; } /** * 关闭文档,生成OFD *

* 注所有文档操作均在close方法执行完成后才会写入文件,打包生成OFD文档。 * 每个打开的文档都应该调用该方法。 * * @throws IOException 文档操作异常 */ @Override public synchronized void close() throws IOException { if (this.closed) { return; } else { closed = true; } try { if (!streamQueue.isEmpty()) { /* * 将流式布局转换为板式布局 */ SegmentationEngine sgmEngine = new SegmentationEngine(pageLayout); StreamingLayoutAnalyzer analyzer = new StreamingLayoutAnalyzer(pageLayout); // 1. 流式布局队列经过分段引擎,获取分段队列 List sgmQueue = sgmEngine.process(streamQueue); // 2. 段队列进入布局分析器,构造基于固定布局的虚拟页面。 List virtualPageList = analyzer.analyze(sgmQueue); vPageList.addAll(virtualPageList); } // 流式集合列表 if (!sPageList.isEmpty()) { for (StreamCollect sCollect : sPageList) { List pageList = sCollect.analyze(pageLayout); vPageList.addAll(pageList); } } // 虚拟页面布局 if (!vPageList.isEmpty()) { DocDir docDefault = ofdDir.obtainDocDefault(); // 创建虚拟页面解析引擎,并持有文档上下文。 VPageParseEngine parseEngine = new VPageParseEngine(pageLayout, docDefault, prm, MaxUnitID); parseEngine.setBeforePageParseHandler(onPageHandler); // 解析虚拟页面 parseEngine.process(vPageList); } if (vPageList.isEmpty() && annotationRender == null && reader == null) { // 虚拟页面为空,也没有注解对象,也不是编辑模式,那么空的操作报错 throw new IllegalStateException("OFD文档中没有页面,无法生成OFD文档"); } if (renderingEndHandler != null) { // 执行渲染结束回调函数 renderingEndHandler.handle(MaxUnitID, ofdDir, operateDocDir.getIndex()); } // 设置最大对象ID cdata.setMaxUnitID(MaxUnitID.get()); // final. 执行打包程序 if (outPath != null) { ofdDir.jar(outPath.toAbsolutePath()); } else if (outStream != null) { ofdDir.jar(outStream); } else { throw new IllegalArgumentException("OFD文档输出地址错误或没有设置输出流"); } } finally { if (reader != null) { reader.close(); } else if (ofdDir != null) { // 清除在生成OFD过程中的工作区产生的文件 ofdDir.clean(); } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy